ayder-cli 2.1.1__tar.gz → 2.1.4__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 (242) hide show
  1. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/PKG-INFO +3 -1
  2. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/config.toml.example +53 -8
  3. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/pyproject.toml +2 -1
  4. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/config.py +38 -4
  5. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/default_context_manager.py +48 -12
  6. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/ollama_context_manager.py +2 -2
  7. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama.py +4 -6
  8. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/themes/claude.py +27 -0
  9. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/themes/original.py +21 -0
  10. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/definition.py +10 -4
  11. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_manager.py +9 -4
  12. ayder_cli-2.1.4/src/ayder_cli/tools/plugin_status.py +33 -0
  13. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/app.py +81 -27
  14. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/commands.py +38 -8
  15. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/keybindings.py +3 -3
  16. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/widgets.py +147 -14
  17. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_coverage.py +13 -9
  18. ayder_cli-2.1.4/tests/core/test_config_provider_logging.py +46 -0
  19. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_default_context_manager.py +133 -0
  20. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager.py +15 -0
  21. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_native.py +33 -0
  22. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_manager.py +82 -0
  23. ayder_cli-2.1.4/tests/tools/test_plugin_status.py +36 -0
  24. ayder_cli-2.1.4/tests/tui/test_input_box.py +114 -0
  25. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_multiline_input.py +37 -40
  26. ayder_cli-2.1.4/tests/tui/test_status_bar_badges.py +70 -0
  27. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_widgets.py +77 -1
  28. ayder_cli-2.1.4/tests/ui/test_plugin_autoenable.py +44 -0
  29. ayder_cli-2.1.4/tests/ui/test_tui_commands_plugin.py +72 -0
  30. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.github/workflows/ci.yml +0 -0
  31. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.github/workflows/python-package.yml +0 -0
  32. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.gitignore +0 -0
  33. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/.python-version +0 -0
  34. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/AGENTS.md +0 -0
  35. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/CHANGELOG.md +0 -0
  36. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/LICENSE +0 -0
  37. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/README.md +0 -0
  38. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/PROJECT_STRUCTURE.md +0 -0
  39. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/README_AGENT_WORKFLOW.md +0 -0
  40. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/README_OLLAMA.md +0 -0
  41. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/docs/cc.png +0 -0
  42. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/__init__.py +0 -0
  43. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/__main__.py +0 -0
  44. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/__init__.py +0 -0
  45. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/callbacks.py +0 -0
  46. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/config.py +0 -0
  47. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/registry.py +0 -0
  48. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/run.py +0 -0
  49. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/runner.py +0 -0
  50. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/agents/tool.py +0 -0
  51. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/README.md +0 -0
  52. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/__init__.py +0 -0
  53. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/execution_policy.py +0 -0
  54. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/message_contract.py +0 -0
  55. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/runtime_factory.py +0 -0
  56. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/application/validation.py +0 -0
  57. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/cli.py +0 -0
  58. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/cli_callbacks.py +0 -0
  59. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/cli_runner.py +0 -0
  60. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/console.py +0 -0
  61. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/cache_monitor.py +0 -0
  62. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/config_migration.py +0 -0
  63. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/context.py +0 -0
  64. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager.py +0 -0
  65. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/context_manager_factory.py +0 -0
  66. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/core/result.py +0 -0
  67. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/logging_config.py +0 -0
  68. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/loops/__init__.py +0 -0
  69. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/loops/chat_loop.py +0 -0
  70. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/parser.py +0 -0
  71. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/process_manager.py +0 -0
  72. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/prompts.py +0 -0
  73. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/__init__.py +0 -0
  74. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/base.py +0 -0
  75. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/claude.py +0 -0
  76. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/deepseek.py +0 -0
  77. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/gemini.py +0 -0
  78. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/glm.py +0 -0
  79. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
  80. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
  81. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
  82. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
  83. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
  84. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
  85. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
  86. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
  87. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
  88. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
  89. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
  90. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/openai.py +0 -0
  91. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/impl/qwen.py +0 -0
  92. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/orchestrator.py +0 -0
  93. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/providers/retry.py +0 -0
  94. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/services/__init__.py +0 -0
  95. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/services/interactions.py +0 -0
  96. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/themes/__init__.py +0 -0
  97. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/__init__.py +0 -0
  98. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/__init__.py +0 -0
  99. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context.py +0 -0
  100. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
  101. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
  102. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
  103. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes.py +0 -0
  104. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
  105. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
  106. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search.py +0 -0
  107. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
  108. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell.py +0 -0
  109. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
  110. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks.py +0 -0
  111. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/tasks_definitions.py +0 -0
  112. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
  113. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
  114. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web.py +0 -0
  115. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
  116. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/execution.py +0 -0
  117. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/hooks.py +0 -0
  118. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/normalization.py +0 -0
  119. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_api.py +0 -0
  120. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/plugin_github.py +0 -0
  121. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/registry.py +0 -0
  122. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/schemas.py +0 -0
  123. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tools/utils.py +0 -0
  124. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/__init__.py +0 -0
  125. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/adapter.py +0 -0
  126. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/helpers.py +0 -0
  127. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/parser.py +0 -0
  128. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/screens.py +0 -0
  129. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/theme_manager.py +0 -0
  130. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/tui/types.py +0 -0
  131. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/ui.py +0 -0
  132. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/src/ayder_cli/version.py +0 -0
  133. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/COVERAGE.md +0 -0
  134. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/__init__.py +0 -0
  135. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/__init__.py +0 -0
  136. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_agent_tools.py +0 -0
  137. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_callbacks.py +0 -0
  138. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_config.py +0 -0
  139. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_integration.py +0 -0
  140. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_registry.py +0 -0
  141. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_run.py +0 -0
  142. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_runner.py +0 -0
  143. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_runner_provider_unavailable.py +0 -0
  144. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/agents/test_tool.py +0 -0
  145. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/application/test_message_contract.py +0 -0
  146. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/application/test_runtime_factory.py +0 -0
  147. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/client/test_main.py +0 -0
  148. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/__init__.py +0 -0
  149. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/test_execution_policy_parity.py +0 -0
  150. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/test_runtime_wiring.py +0 -0
  151. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/convergence/test_validation_path.py +0 -0
  152. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_cache_monitor.py +0 -0
  153. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config.py +0 -0
  154. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_migration.py +0 -0
  155. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_retry.py +0 -0
  156. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_config_v2.py +0 -0
  157. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_context_manager_factory.py +0 -0
  158. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_default_context_manager_should_compact.py +0 -0
  159. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
  160. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_ollama_context_manager_max_history.py +0 -0
  161. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_parameter_aliasing.py +0 -0
  162. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/core/test_parser.py +0 -0
  163. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/__init__.py +0 -0
  164. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/test_base.py +0 -0
  165. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_hook.py +0 -0
  166. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
  167. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/__init__.py +0 -0
  168. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/__init__.py +0 -0
  169. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_base.py +0 -0
  170. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
  171. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_errors.py +0 -0
  172. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_fallback.py +0 -0
  173. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
  174. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
  175. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_matrix.py +0 -0
  176. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_minimax.py +0 -0
  177. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
  178. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
  179. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/ollama_drivers/test_registry.py +0 -0
  180. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_glm_async.py +0 -0
  181. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_inspector.py +0 -0
  182. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml.py +0 -0
  183. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_autoroute.py +0 -0
  184. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_ollama_xml_usage.py +0 -0
  185. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_orchestrator_capabilities.py +0 -0
  186. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_qwen_async.py +0 -0
  187. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_retry_classifier.py +0 -0
  188. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_retry_integration.py +0 -0
  189. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/providers/test_retrying_provider.py +0 -0
  190. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/services/test_boundary.py +0 -0
  191. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/services/test_interaction_sink.py +0 -0
  192. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_cli.py +0 -0
  193. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_cli_callbacks.py +0 -0
  194. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_context.py +0 -0
  195. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_env_manager.py +0 -0
  196. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_logging_config.py +0 -0
  197. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_notes.py +0 -0
  198. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_process_manager.py +0 -0
  199. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/test_tasks.py +0 -0
  200. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/__init__.py +0 -0
  201. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_agent_notes.py +0 -0
  202. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_definition_discovery.py +0 -0
  203. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_di_injection.py +0 -0
  204. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_impl.py +0 -0
  205. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_impl_coverage.py +0 -0
  206. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_path_security.py +0 -0
  207. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_api.py +0 -0
  208. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_github.py +0 -0
  209. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_plugin_loading.py +0 -0
  210. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_registry.py +0 -0
  211. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_registry_coverage.py +0 -0
  212. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_result.py +0 -0
  213. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_schemas.py +0 -0
  214. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_search_codebase.py +0 -0
  215. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_utils.py +0 -0
  216. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tools/test_web.py +0 -0
  217. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/__init__.py +0 -0
  218. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_agent_list_screen.py +0 -0
  219. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_do_clear_coordination.py +0 -0
  220. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_follow_mode.py +0 -0
  221. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_help_screen.py +0 -0
  222. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_keybindings.py +0 -0
  223. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_multi_select_screen.py +0 -0
  224. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_paste.py +0 -0
  225. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_pending_compact_consumer.py +0 -0
  226. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_pull_delivery.py +0 -0
  227. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/tui/test_turn_consumer.py +0 -0
  228. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_confirm_screen.py +0 -0
  229. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_diff_preview.py +0 -0
  230. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_provider_switch.py +0 -0
  231. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_run_tui_startup.py +0 -0
  232. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_assistant_sanitization.py +0 -0
  233. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_chat_loop.py +0 -0
  234. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_agent.py +0 -0
  235. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_logging.py +0 -0
  236. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_commands_provider.py +0 -0
  237. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_helpers.py +0 -0
  238. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_temporal_command.py +0 -0
  239. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_tui_widgets.py +0 -0
  240. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_ui.py +0 -0
  241. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_ui_coverage.py +0 -0
  242. {ayder_cli-2.1.1 → ayder_cli-2.1.4}/tests/ui/test_ui_verbose.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayder-cli
3
- Version: 2.1.1
3
+ Version: 2.1.4
4
4
  Summary: AI agent for any LLMs
5
5
  Project-URL: Homepage, https://github.com/ayder/ayder-cli
6
6
  Project-URL: Repository, https://github.com/ayder/ayder-cli.git
@@ -34,6 +34,8 @@ Provides-Extra: glm
34
34
  Requires-Dist: zhipuai; extra == 'glm'
35
35
  Provides-Extra: google
36
36
  Requires-Dist: google-genai; extra == 'google'
37
+ Provides-Extra: mcp
38
+ Requires-Dist: mcp; extra == 'mcp'
37
39
  Provides-Extra: qwen
38
40
  Requires-Dist: dashscope; extra == 'qwen'
39
41
  Description-Content-Type: text/markdown
@@ -48,9 +48,29 @@
48
48
  # `export OLLAMA_API_KEY="<key>"`. The api_key config field is INERT on the
49
49
  # native path, so a missing env var ⇒ 401 Unauthorized.
50
50
  #
51
- # NOTE on `think`: value is model/driver-dependent. Recent Ollama accepts
52
- # true/false for most models and "low"|"medium"|"high" for reasoning
53
- # models like deepseek-v4-pro. Adjust per model if a value is rejected.
51
+ # NOTE on `think`: on the native `ollama` driver, ayder hardcodes thinking ON
52
+ # (think=True) and does NOT forward the configured level — so a
53
+ # `think = "high"` below documents intent, but the "low/medium/high"
54
+ # granularity is not applied. The `openai` driver sends no reasoning-effort
55
+ # parameter at all.
56
+ #
57
+ # NOTE on tuning knobs — which actually take effect depends on the DRIVER. These
58
+ # agents run on the native `ollama` driver, which ignores most of them:
59
+ # • temperature — forwarded by NO driver yet ⇒ no effect anywhere.
60
+ # • max_output_tokens — NOT sent on the ollama path (Ollama sizes
61
+ # generation itself); honored only by the
62
+ # openai-family drivers as max_tokens. Default 4096.
63
+ # • think — hardcoded ON for ollama (level ignored); openai
64
+ # sends none.
65
+ # • num_ctx — on ollama it is provisional only (the real window
66
+ # is auto-detected from /api/show); on the openai
67
+ # path it sizes the context-manager budget.
68
+ # Default 65536.
69
+ # The per-agent temperature / max_output_tokens / think values below are
70
+ # therefore largely INERT on this Ollama pipeline — they are kept to
71
+ # document intent and to carry over if you switch an agent to an `openai`
72
+ # profile. To actually tune these, use the `openai` driver — see
73
+ # [llm.openai] below.
54
74
  # =============================================================================
55
75
 
56
76
  config_version = "2.0"
@@ -62,7 +82,12 @@ verbose = false
62
82
  prompt = "STANDARD" # MINIMAL | STANDARD | EXTENDED
63
83
  max_background_processes = 5 # cap on concurrent background processes (e.g. test runs)
64
84
  max_output_tokens = 8192 # orchestrator response cap
65
- max_history_messages = 30
85
+ # Max conversation messages kept per turn. 0 = unlimited (default): the context
86
+ # manager's token budget + compaction are the bound, so the model uses its real
87
+ # window. A small fixed cap (the old default was 30) thrashes a tiny history
88
+ # window on a large-context model and wastes most of it. Set a finite number
89
+ # only if you want a hard message cap regardless of window size.
90
+ max_history_messages = 0
66
91
  agent_timeout = 1800 # seconds before a background agent is cancelled (30 min; reasoning+coding can be slow)
67
92
  # Tools the agents inherit. A coding harness needs file/shell/search (core),
68
93
  # tasks/notes (metadata), background process control for test suites
@@ -77,10 +102,16 @@ retention = "7 days"
77
102
 
78
103
  [context_manager]
79
104
  enabled = true
80
- # On the native Ollama path, OllamaContextManager auto-detects the model's real
81
- # window from /api/show; max_context_tokens is ignored there and only matters on
82
- # the openai/non-ollama path. reserve_ratio + compaction_threshold ARE read.
83
- max_context_tokens = 131072
105
+ # Destructive tool-result compression is OPT-IN and OFF by default. It summarizes
106
+ # old tool results in place (lossy) and thrashes the KV cache, so leave it off
107
+ # unless you specifically want it; eviction will replace it in a later release.
108
+ enable_compression = false
109
+ # max_context_tokens is OPTIONAL. When unset (recommended), the budget is derived
110
+ # from the active model's num_ctx. It is read only on the openai/non-ollama path
111
+ # (DefaultContextManager); the native Ollama path auto-detects the real window
112
+ # from /api/show and ignores it. reserve_ratio + compaction_threshold ARE read on
113
+ # both paths.
114
+ # max_context_tokens = 131072
84
115
  reserve_ratio = 0.30
85
116
  compaction_threshold = 0.80
86
117
 
@@ -118,6 +149,20 @@ num_ctx = 32768 # keep modest: local VRAM is the constraint
118
149
  # model = "gemma4:31b-cloud"
119
150
  # num_ctx = 131072
120
151
 
152
+ # OpenAI-compatible driver. Unlike the `ollama` driver above, this one HONORS the
153
+ # tuning knobs (defaults shown). Use it for api.openai.com, Azure, vLLM,
154
+ # llama.cpp, LM Studio, or Ollama's own `/v1` endpoint. Point an agent at it with
155
+ # `provider = "openai"`.
156
+ # [llm.openai]
157
+ # driver = "openai"
158
+ # base_url = "https://api.openai.com/v1" # OpenAI-compatible endpoint
159
+ # api_key = "sk-..." # your key (or "ollama"/"none" for local servers)
160
+ # model = "gpt-5.5"
161
+ # num_ctx = 65536 # sizes the context-manager budget (default 65536); NOT sent to the API
162
+ # max_output_tokens = 8192 # -> max_tokens / max_completion_tokens (default 4096); keep WELL below num_ctx
163
+ # stop_sequences = [] # -> stop (default [])
164
+ # # temperature / think are NOT applied by the openai driver today (no-ops) — omitted on purpose.
165
+
121
166
  # =============================================================================
122
167
  # Agents
123
168
  # =============================================================================
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ayder-cli"
7
- version = "2.1.1"
7
+ version = "2.1.4"
8
8
  description = "AI agent for any LLMs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -37,6 +37,7 @@ google = ["google-genai"]
37
37
  qwen = ["dashscope"]
38
38
  glm = ["zhipuai"]
39
39
  all = ["ayder-cli[anthropic,google,qwen,glm]"]
40
+ mcp = ["mcp"]
40
41
 
41
42
  [project.urls]
42
43
  Homepage = "https://github.com/ayder/ayder-cli"
@@ -1,8 +1,11 @@
1
+ import logging
1
2
  import tomllib
2
3
  from pathlib import Path
3
4
  from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
4
5
  from typing import Any, Callable, Dict
5
6
 
7
+ logger = logging.getLogger(__name__)
8
+
6
9
  CONFIG_DIR = Path("~/.ayder").expanduser()
7
10
  CONFIG_PATH = CONFIG_DIR / "config.toml"
8
11
 
@@ -174,12 +177,19 @@ class ContextManagerConfigSection(BaseModel):
174
177
  model_config = ConfigDict(frozen=True)
175
178
 
176
179
  enabled: bool = Field(default=True)
177
- max_context_tokens: int = Field(default=8192)
180
+ # Optional: when unset, the context manager derives the budget from the
181
+ # model's num_ctx. The old fixed 8192 default predates large-context models.
182
+ max_context_tokens: int | None = Field(default=None)
178
183
  reserve_ratio: float = Field(default=0.30)
179
184
  compaction_threshold: float = Field(default=0.70)
180
185
  tool_result_compress_age: int = Field(default=5)
181
186
  max_tool_result_length: int = Field(default=2048)
182
187
  compress_tool_results: bool = Field(default=True)
188
+ # Master switch for destructive tool-result compression. Compression is
189
+ # known to be too aggressive (it summarizes old tool results in place and
190
+ # thrashes the KV cache), so it is OPT-IN: disabled unless explicitly set.
191
+ # Eviction will replace it in a later phase.
192
+ enable_compression: bool = Field(default=False)
183
193
 
184
194
  @field_validator("reserve_ratio", "compaction_threshold")
185
195
  @classmethod
@@ -190,8 +200,8 @@ class ContextManagerConfigSection(BaseModel):
190
200
 
191
201
  @field_validator("max_context_tokens")
192
202
  @classmethod
193
- def validate_positive(cls, v: int) -> int:
194
- if v <= 0:
203
+ def validate_positive(cls, v: int | None) -> int | None:
204
+ if v is not None and v <= 0:
195
205
  raise ValueError("max_context_tokens must be positive")
196
206
  return v
197
207
 
@@ -278,7 +288,10 @@ class Config(BaseModel):
278
288
  logging_retention: str = Field(default="7 days")
279
289
  max_background_processes: int = Field(default=5)
280
290
  max_output_tokens: int = Field(default=4096)
281
- max_history_messages: int = Field(default=30)
291
+ # 0 = unlimited: let the context manager's token budget + compaction be the
292
+ # bound. The old fixed 30 predates large-context models and thrashes a tiny
293
+ # history window on a big context.
294
+ max_history_messages: int = Field(default=0)
282
295
  prompt: str = Field(default="STANDARD")
283
296
  chat_protocol: str = Field(default="ollama")
284
297
  stop_sequences: list[str] = Field(default_factory=list)
@@ -309,9 +322,25 @@ class Config(BaseModel):
309
322
  if isinstance(llm_section, dict):
310
323
  profile = llm_section.get(provider)
311
324
  if not isinstance(profile, dict):
325
+ available = ", ".join(sorted(llm_section)) or "<none>"
312
326
  default_profile = llm_section.get(DEFAULTS["provider"])
313
327
  if isinstance(default_profile, dict):
314
328
  profile = default_profile
329
+ logger.error(
330
+ "Provider %r has no [llm.%s] profile; falling back to the "
331
+ "default [llm.%s] profile. Available profiles: %s. Fix the "
332
+ "provider name or add an [llm.%s] section.",
333
+ provider, provider, DEFAULTS["provider"], available, provider,
334
+ )
335
+ else:
336
+ logger.error(
337
+ "Provider %r has no [llm.%s] profile and no default "
338
+ "[llm.%s] profile exists; the driver will default to "
339
+ "'openai' with built-in endpoint defaults. Available "
340
+ "profiles: %s. Fix the provider name or add an [llm.%s] "
341
+ "section.",
342
+ provider, provider, DEFAULTS["provider"], available, provider,
343
+ )
315
344
  if isinstance(profile, dict):
316
345
  new_data.update(profile)
317
346
  new_data.pop("llm", None)
@@ -359,6 +388,11 @@ class Config(BaseModel):
359
388
  "qwen", "dashscope", "glm", "zhipu",
360
389
  )
361
390
  if v not in valid:
391
+ logger.error(
392
+ "Unsupported driver %r; must be one of: %s. Fix the driver in "
393
+ "your [llm.<provider>] profile.",
394
+ v, ", ".join(valid),
395
+ )
362
396
  raise ValueError(f"driver must be one of: {', '.join(valid)}")
363
397
  return v
364
398
 
@@ -19,6 +19,10 @@ if TYPE_CHECKING:
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
22
+ # Fallback context budget used only when neither an explicit max_context_tokens
23
+ # nor a model num_ctx is available (e.g. a bare config section in tests).
24
+ DEFAULT_MAX_CONTEXT_TOKENS = 65536
25
+
22
26
 
23
27
  class MessageTier(Enum):
24
28
  """Importance tiers for messages."""
@@ -147,10 +151,34 @@ class DefaultContextManager:
147
151
  elif isinstance(config, ContextManagerConfigSection):
148
152
  self._config = config
149
153
  else:
150
- max_tokens = getattr(config, "num_ctx", 8192)
151
- if max_tokens <= 0:
152
- max_tokens = 8192
153
- self._config = ContextManagerConfigSection(max_context_tokens=max_tokens)
154
+ self._config = ContextManagerConfigSection()
155
+
156
+ # Resolve the effective context budget. An explicit max_context_tokens
157
+ # wins; otherwise derive it from the model's num_ctx so the budget tracks
158
+ # the real window instead of the legacy fixed default.
159
+ configured = self._config.max_context_tokens
160
+ num_ctx = getattr(config, "num_ctx", None)
161
+ if isinstance(configured, int) and configured > 0:
162
+ self._max_context_tokens = configured
163
+ logger.info(
164
+ "Context budget: %d tokens (explicit max_context_tokens; num_ctx=%s)",
165
+ configured, num_ctx,
166
+ )
167
+ elif isinstance(num_ctx, int) and num_ctx > 0:
168
+ self._max_context_tokens = num_ctx
169
+ logger.info(
170
+ "Context budget: %d tokens (derived from num_ctx; "
171
+ "max_context_tokens unset)",
172
+ num_ctx,
173
+ )
174
+ else:
175
+ self._max_context_tokens = DEFAULT_MAX_CONTEXT_TOKENS
176
+ logger.warning(
177
+ "Context budget: could not read num_ctx or max_context_tokens; "
178
+ "falling back to default %d tokens. Recommend setting num_ctx "
179
+ "(or [context_manager] max_context_tokens) in your config.",
180
+ DEFAULT_MAX_CONTEXT_TOKENS,
181
+ )
154
182
 
155
183
  provider = getattr(config, "provider", "openai")
156
184
  self._counter = TokenCounter(model=model, provider=provider)
@@ -214,7 +242,7 @@ class DefaultContextManager:
214
242
  response arrives. See opus47.md finding #6.
215
243
  """
216
244
  if self._last_prompt_tokens:
217
- ceiling = self._config.max_context_tokens
245
+ ceiling = self._max_context_tokens
218
246
  return self._last_prompt_tokens >= ceiling * self._config.compaction_threshold
219
247
  return self.should_trigger_summarization()
220
248
 
@@ -227,8 +255,8 @@ class DefaultContextManager:
227
255
  return self._stats_cache
228
256
 
229
257
  total = self._calculate_total_tokens()
230
- available = max(0, self._config.max_context_tokens - total)
231
- utilization = (total / self._config.max_context_tokens) * 100
258
+ available = max(0, self._max_context_tokens - total)
259
+ utilization = (total / self._max_context_tokens) * 100
232
260
 
233
261
  self._stats_cache = ContextStats(
234
262
  total_tokens=total,
@@ -245,7 +273,7 @@ class DefaultContextManager:
245
273
 
246
274
  @property
247
275
  def reserve(self) -> int:
248
- return int(self._config.max_context_tokens * self._config.reserve_ratio)
276
+ return int(self._max_context_tokens * self._config.reserve_ratio)
249
277
 
250
278
  def count_tokens(self, text: str) -> int:
251
279
  """Legacy support."""
@@ -362,18 +390,24 @@ class DefaultContextManager:
362
390
  return False
363
391
 
364
392
  total = self._calculate_total_tokens()
365
- utilization = total / self._config.max_context_tokens
393
+ utilization = total / self._max_context_tokens
366
394
  return utilization >= self._config.compaction_threshold
367
395
 
368
396
  def should_trigger_compression(self) -> bool:
397
+ if not getattr(self._config, "enable_compression", False):
398
+ return False
369
399
  if not self._config.enabled or not self._config.compress_tool_results:
370
400
  return False
371
401
 
372
402
  total = self._calculate_total_tokens()
373
- utilization = total / self._config.max_context_tokens
403
+ utilization = total / self._max_context_tokens
374
404
  return utilization >= self._config.compaction_threshold
375
405
 
376
406
  def compress_old_results(self, age_threshold: int | None = None) -> int:
407
+ # Compression is opt-in. Disabled by default because it destructively
408
+ # summarizes old tool results in place and thrashes the KV cache.
409
+ if not getattr(self._config, "enable_compression", False):
410
+ return 0
377
411
  threshold = (
378
412
  age_threshold
379
413
  if age_threshold is not None
@@ -449,10 +483,12 @@ class DefaultContextManager:
449
483
  return overhead
450
484
 
451
485
  def _available_budget(self, overhead: int) -> int:
452
- reserve = int(self._config.max_context_tokens * self._config.reserve_ratio)
453
- return self._config.max_context_tokens - overhead - reserve
486
+ reserve = int(self._max_context_tokens * self._config.reserve_ratio)
487
+ return self._max_context_tokens - overhead - reserve
454
488
 
455
489
  def _auto_compress(self) -> None:
490
+ if not getattr(self._config, "enable_compression", False):
491
+ return
456
492
  if not self._config.compress_tool_results:
457
493
  return
458
494
  if self.should_trigger_compression():
@@ -237,8 +237,8 @@ class OllamaContextManager:
237
237
  cache_state = f", cache={status.state}({status.hit_ratio:.0%})"
238
238
 
239
239
  logger.info(
240
- "Context: %d/%d tokens (%.0f%%), completion=%d, speed=%s%s",
241
- self._real_prompt_tokens, ceiling, utilization,
240
+ "Context[%s]: %d/%d tokens (%.0f%%), completion=%d, speed=%s%s",
241
+ self._model or "?", self._real_prompt_tokens, ceiling, utilization,
242
242
  self._real_completion_tokens, prompt_speed, cache_state,
243
243
  )
244
244
 
@@ -146,8 +146,8 @@ class OllamaProvider(AIProvider):
146
146
  tools: list[dict[str, Any]] | None,
147
147
  options: dict[str, Any] | None,
148
148
  ) -> AsyncGenerator[NormalizedStreamChunk, None]:
149
- opts = options or {}
150
- num_ctx = opts.get("num_ctx", 65536)
149
+ # Let Ollama size the context window from the model itself (recent
150
+ # versions do this automatically); do not force num_ctx.
151
151
  ollama_tools = self._convert_tools(tools) if tools else None
152
152
  ollama_messages = self._convert_messages(messages)
153
153
 
@@ -155,7 +155,6 @@ class OllamaProvider(AIProvider):
155
155
  model=model,
156
156
  messages=ollama_messages,
157
157
  tools=ollama_tools,
158
- options={"num_ctx": num_ctx},
159
158
  keep_alive=-1,
160
159
  think=True,
161
160
  stream=True,
@@ -204,8 +203,8 @@ class OllamaProvider(AIProvider):
204
203
  tools: list[dict[str, Any]] | None,
205
204
  options: dict[str, Any] | None,
206
205
  ) -> AsyncGenerator[NormalizedStreamChunk, None]:
207
- opts = options or {}
208
- num_ctx = opts.get("num_ctx", 65536)
206
+ # Let Ollama size the context window from the model itself; do not
207
+ # force num_ctx.
209
208
  formatted_messages = driver.render_tools_into_messages(messages, tools or [])
210
209
  ollama_messages = self._convert_messages(formatted_messages)
211
210
 
@@ -213,7 +212,6 @@ class OllamaProvider(AIProvider):
213
212
  model=model,
214
213
  messages=ollama_messages,
215
214
  tools=None,
216
- options={"num_ctx": num_ctx},
217
215
  keep_alive=-1,
218
216
  think=True,
219
217
  stream=True,
@@ -633,6 +633,27 @@ AgentPanel .agent-detail {
633
633
  padding: 0 0 0 4;
634
634
  }
635
635
 
636
+ /* ThinkingPanel - Scrollable panel showing the model's reasoning stream.
637
+ Toggled with Ctrl+T. Taller than the tool/agent panels because reasoning is
638
+ long-form. Targets 'ThinkingPanel' in widgets.py. */
639
+ ThinkingPanel {
640
+ layout: vertical;
641
+ height: auto;
642
+ max-height: 60%;
643
+ background: #0a0a18;
644
+ border-top: solid #333350;
645
+ padding: 1 1;
646
+ overflow-y: scroll;
647
+ display: none;
648
+ }
649
+
650
+ ThinkingPanel .thinking-content {
651
+ height: auto;
652
+ color: #8888aa;
653
+ text-style: italic;
654
+ padding: 0 1;
655
+ }
656
+
636
657
  /* AyderApp - The root application screen. */
637
658
  Screen {
638
659
  layout: vertical;
@@ -669,6 +690,12 @@ Screen:inline {
669
690
  width: 100%;
670
691
  }
671
692
 
693
+ #thinking-panel {
694
+ min-height: 0;
695
+ max-height: 60%;
696
+ width: 100%;
697
+ }
698
+
672
699
  #input-bar {
673
700
  height: auto;
674
701
  min-height: 1;
@@ -514,6 +514,27 @@ ToolPanel .tool-item.completed {
514
514
  color: $success;
515
515
  }
516
516
 
517
+ /* ThinkingPanel - Scrollable reasoning stream, toggled with Ctrl+T.
518
+ Taller than the tool/agent panels because reasoning is long-form. */
519
+ ThinkingPanel {
520
+ layout: vertical;
521
+ height: auto;
522
+ min-height: 6;
523
+ max-height: 24;
524
+ background: $surface-darken-1;
525
+ border-top: heavy $primary;
526
+ padding: 1 1;
527
+ overflow-y: scroll;
528
+ display: none;
529
+ }
530
+
531
+ ThinkingPanel .thinking-content {
532
+ height: auto;
533
+ color: $text-muted;
534
+ text-style: italic;
535
+ padding: 0 1;
536
+ }
537
+
517
538
  /* AyderApp - Main application */
518
539
  Screen {
519
540
  layout: vertical;
@@ -168,7 +168,15 @@ def _discover_definitions() -> Tuple["ToolDefinition", ...]:
168
168
 
169
169
  _BUILTIN_DEFINITIONS: Tuple[ToolDefinition, ...] = _discover_definitions()
170
170
 
171
- # Phase 1: Merge global plugins at import time
171
+ # Phase 1: Merge global plugins at import time.
172
+ #
173
+ # Discovery runs *inside this module's own body*, so a plugin whose definitions
174
+ # module reads ``TOOL_DEFINITIONS`` at import time (e.g. to avoid tool-name
175
+ # collisions, as mcp-tool does) would otherwise hit a circular import — the name
176
+ # isn't bound yet. Bind it provisionally to the builtins first; those are the
177
+ # only definitions that exist during discovery anyway. It is reassigned to the
178
+ # full set (builtins + plugins) immediately after discovery completes.
179
+ TOOL_DEFINITIONS: Tuple[ToolDefinition, ...] = _BUILTIN_DEFINITIONS
172
180
  _GLOBAL_PLUGIN_DEFS: Tuple[ToolDefinition, ...] = ()
173
181
  _PLUGIN_HANDLERS: Dict[str, Any] = {}
174
182
 
@@ -179,9 +187,7 @@ try:
179
187
  except Exception as e:
180
188
  logger.warning(f"Failed to load global plugins: {e}")
181
189
 
182
- TOOL_DEFINITIONS: Tuple[ToolDefinition, ...] = (
183
- _BUILTIN_DEFINITIONS + _GLOBAL_PLUGIN_DEFS
184
- )
190
+ TOOL_DEFINITIONS = _BUILTIN_DEFINITIONS + _GLOBAL_PLUGIN_DEFS
185
191
  TOOL_DEFINITIONS_BY_NAME: Dict[str, ToolDefinition] = {
186
192
  td.name: td for td in TOOL_DEFINITIONS
187
193
  }
@@ -147,10 +147,15 @@ def install_plugin_from_local(
147
147
  "Use --force to overwrite."
148
148
  )
149
149
 
150
- # Copy plugin files
151
- if target_dir.exists():
152
- shutil.rmtree(target_dir)
153
- shutil.copytree(source_dir, target_dir)
150
+ # Copy plugin files. When the source IS the install target — e.g. reinstalling
151
+ # an already-installed plugin from its own directory with --force, which is
152
+ # exactly what the missing-dependency warning recommends — the files are
153
+ # already in place. Skip the copy so we don't rmtree the source out from under
154
+ # ourselves (the dependency reinstall + registry refresh below still run).
155
+ if source_dir.resolve() != target_dir.resolve():
156
+ if target_dir.exists():
157
+ shutil.rmtree(target_dir)
158
+ shutil.copytree(source_dir, target_dir)
154
159
 
155
160
  # Update plugins.json
156
161
  data = read_plugins_json(plugins_dir)
@@ -0,0 +1,33 @@
1
+ """Plugin status-badge registry.
2
+
3
+ A lightweight, process-global registry that plugins use to publish a status
4
+ badge (a short label plus a colour) for display in the TUI status bar. The
5
+ mcp-tool plugin, for example, calls ``set_status("mcp", "MCP: news-digest",
6
+ "green")`` once it connects to its MCP servers, and ``set_status("mcp", "MCP",
7
+ "red")`` when configured servers are unreachable.
8
+
9
+ The TUI ``StatusBar`` reads :func:`get_all` to render the badges. Keeping this
10
+ in ayder core (rather than inside a plugin) means the badge survives even when
11
+ the plugin's own modules are loaded under throwaway names during discovery, and
12
+ gives the plugin a stable import target instead of a no-op fallback.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ # plugin name -> (label, colour). Colour is a Rich colour/style name ("green").
18
+ _status: dict[str, tuple[str, str]] = {}
19
+
20
+
21
+ def set_status(name: str, label: str, color: str) -> None:
22
+ """Register or update a plugin's status badge."""
23
+ _status[name] = (label, color)
24
+
25
+
26
+ def get_all() -> dict[str, tuple[str, str]]:
27
+ """Return a copy of all registered status badges (name -> (label, color))."""
28
+ return dict(_status)
29
+
30
+
31
+ def clear() -> None:
32
+ """Remove all registered badges (primarily for tests)."""
33
+ _status.clear()