ayder-cli 2.3.1__tar.gz → 2.4.1__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 (334) hide show
  1. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/AGENTS.md +17 -0
  2. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/PKG-INFO +13 -4
  3. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/README.md +12 -3
  4. ayder_cli-2.4.1/docs/GATE.md +28 -0
  5. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/PROJECT_STRUCTURE.md +6 -3
  6. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/config.toml.example +10 -1
  7. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/pyproject.toml +1 -1
  8. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/check_exception_handlers.py +1 -1
  9. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/check_log_arguments.py +4 -4
  10. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/execution_policy.py +24 -3
  11. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/validation.py +6 -0
  12. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/config.py +11 -1
  13. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/config_migration.py +2 -0
  14. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/context.py +33 -0
  15. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/default_context_manager.py +33 -27
  16. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/ollama_context_manager.py +122 -30
  17. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/loops/chat_loop.py +438 -236
  18. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/base.py +2 -1
  19. ayder_cli-2.4.1/src/ayder_cli/providers/evren.py +61 -0
  20. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/claude.py +26 -7
  21. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/gemini.py +26 -22
  22. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/openai.py +4 -9
  23. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/themes/ayder.py +37 -0
  24. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/themes/claude.py +37 -0
  25. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/context.py +36 -5
  26. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/note_tool.py +22 -0
  27. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/notes.py +33 -10
  28. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/task_tool.py +29 -1
  29. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/tasks.py +59 -13
  30. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/normalization.py +48 -29
  31. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/app.py +2 -2
  32. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/commands.py +41 -2
  33. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/screens.py +38 -3
  34. ayder_cli-2.4.1/tests/application/test_argument_canonicalization.py +136 -0
  35. ayder_cli-2.4.1/tests/core/test_default_config_evren.py +44 -0
  36. ayder_cli-2.4.1/tests/core/test_default_context_newest_unit.py +133 -0
  37. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_event_emission.py +3 -2
  38. ayder_cli-2.4.1/tests/core/test_max_output_tokens_default.py +121 -0
  39. ayder_cli-2.4.1/tests/core/test_ollama_compaction_persistence.py +235 -0
  40. ayder_cli-2.4.1/tests/loops/test_tool_argument_integrity.py +247 -0
  41. ayder_cli-2.4.1/tests/loops/test_tool_batch_cancellation.py +506 -0
  42. ayder_cli-2.4.1/tests/providers/test_claude_stream_tool_calls.py +242 -0
  43. ayder_cli-2.4.1/tests/providers/test_evren.py +79 -0
  44. ayder_cli-2.4.1/tests/providers/test_gemini_function_calls.py +179 -0
  45. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_native.py +16 -0
  46. ayder_cli-2.4.1/tests/providers/test_openai_list_models.py +47 -0
  47. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_exception_gate.py +7 -4
  48. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_log_argument_gate.py +6 -6
  49. ayder_cli-2.4.1/tests/tools/test_context_storage_confinement.py +150 -0
  50. ayder_cli-2.4.1/tests/tools/test_notes_confinement.py +129 -0
  51. ayder_cli-2.4.1/tests/tools/test_task_path_confinement.py +145 -0
  52. ayder_cli-2.4.1/tests/tui/test_evren_terms.py +230 -0
  53. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/uv.lock +1 -1
  54. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.github/workflows/ci.yml +0 -0
  55. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.github/workflows/python-package.yml +0 -0
  56. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.gitignore +0 -0
  57. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.python-version +0 -0
  58. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/LICENSE +0 -0
  59. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/README_AGENT_WORKFLOW.md +0 -0
  60. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/README_OLLAMA.md +0 -0
  61. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/cc.png +0 -0
  62. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/plans/2026-06-26-file-editor-improvements.md +0 -0
  63. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/plans/2026-06-26-session-save-resume.md +0 -0
  64. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/specs/2026-06-26-file-editor-improvements-design.md +0 -0
  65. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/specs/2026-06-26-session-save-resume-design.md +0 -0
  66. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/check_logging_imports.py +0 -0
  67. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/exception_baseline.txt +0 -0
  68. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/find_percent_logging.py +0 -0
  69. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/log_argument_baseline.txt +0 -0
  70. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/logging_gates.py +0 -0
  71. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/__init__.py +0 -0
  72. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/__main__.py +0 -0
  73. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/__init__.py +0 -0
  74. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/callbacks.py +0 -0
  75. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/config.py +0 -0
  76. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/registry.py +0 -0
  77. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/run.py +0 -0
  78. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/runner.py +0 -0
  79. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/tool.py +0 -0
  80. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/worktree.py +0 -0
  81. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/README.md +0 -0
  82. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/__init__.py +0 -0
  83. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/message_contract.py +0 -0
  84. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/runtime_factory.py +0 -0
  85. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/cli.py +0 -0
  86. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/cli_callbacks.py +0 -0
  87. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/cli_runner.py +0 -0
  88. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/console.py +0 -0
  89. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/cache_monitor.py +0 -0
  90. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/context_manager.py +0 -0
  91. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/context_manager_factory.py +0 -0
  92. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/reasoning.py +0 -0
  93. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/result.py +0 -0
  94. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/session.py +0 -0
  95. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/diagnostics.py +0 -0
  96. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/log.py +0 -0
  97. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/logging_config.py +0 -0
  98. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/loops/__init__.py +0 -0
  99. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/masking.py +0 -0
  100. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/parser.py +0 -0
  101. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/process_manager.py +0 -0
  102. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/prompts.py +0 -0
  103. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/__init__.py +0 -0
  104. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/deepseek.py +0 -0
  105. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/glm.py +0 -0
  106. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama.py +0 -0
  107. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
  108. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
  109. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
  110. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
  111. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
  112. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
  113. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
  114. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
  115. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
  116. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
  117. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
  118. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/qwen.py +0 -0
  119. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/orchestrator.py +0 -0
  120. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/retry.py +0 -0
  121. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/services/__init__.py +0 -0
  122. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/services/interactions.py +0 -0
  123. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/services/messaging_inbox.py +0 -0
  124. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/themes/__init__.py +0 -0
  125. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/__init__.py +0 -0
  126. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/__init__.py +0 -0
  127. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/background_process_tool.py +0 -0
  128. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
  129. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
  130. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
  131. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
  132. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
  133. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/search.py +0 -0
  134. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
  135. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/shell.py +0 -0
  136. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
  137. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/skill.py +0 -0
  138. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/skill_definitions.py +0 -0
  139. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/task_definitions.py +0 -0
  140. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
  141. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
  142. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/web.py +0 -0
  143. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
  144. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/definition.py +0 -0
  145. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/execution.py +0 -0
  146. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/hooks.py +0 -0
  147. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/mcp.py +0 -0
  148. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/mcp_client.py +0 -0
  149. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_api.py +0 -0
  150. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_github.py +0 -0
  151. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_manager.py +0 -0
  152. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_status.py +0 -0
  153. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/registry.py +0 -0
  154. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/schemas.py +0 -0
  155. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/utils.py +0 -0
  156. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/__init__.py +0 -0
  157. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/adapter.py +0 -0
  158. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/helpers.py +0 -0
  159. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/keybindings.py +0 -0
  160. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/parser.py +0 -0
  161. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/rendering.py +0 -0
  162. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/theme_manager.py +0 -0
  163. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/types.py +0 -0
  164. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/widgets.py +0 -0
  165. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/ui.py +0 -0
  166. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/version.py +0 -0
  167. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/COVERAGE.md +0 -0
  168. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/__init__.py +0 -0
  169. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/__init__.py +0 -0
  170. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_agent_consolidated.py +0 -0
  171. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_agent_severity.py +0 -0
  172. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_agent_tools.py +0 -0
  173. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_callbacks.py +0 -0
  174. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_config.py +0 -0
  175. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_integration.py +0 -0
  176. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_queue.py +0 -0
  177. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_registry.py +0 -0
  178. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_run.py +0 -0
  179. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_runner.py +0 -0
  180. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_runner_provider_unavailable.py +0 -0
  181. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_tool.py +0 -0
  182. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_worktree_helpers.py +0 -0
  183. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_worktree_isolation.py +0 -0
  184. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/application/test_execution_result_truthfulness.py +0 -0
  185. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/application/test_message_contract.py +0 -0
  186. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/application/test_runtime_factory.py +0 -0
  187. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/client/test_main.py +0 -0
  188. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/conftest.py +0 -0
  189. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/__init__.py +0 -0
  190. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/test_execution_policy_parity.py +0 -0
  191. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/test_runtime_wiring.py +0 -0
  192. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/test_validation_path.py +0 -0
  193. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_asyncio_bridge.py +0 -0
  194. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_cache_monitor.py +0 -0
  195. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config.py +0 -0
  196. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_coverage.py +0 -0
  197. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_migration.py +0 -0
  198. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_provider_logging.py +0 -0
  199. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_retry.py +0 -0
  200. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_v2.py +0 -0
  201. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_context_manager_factory.py +0 -0
  202. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_default_context_manager.py +0 -0
  203. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_default_context_manager_should_compact.py +0 -0
  204. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_event_stream.py +0 -0
  205. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_exception_guarantee.py +0 -0
  206. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_exception_hooks.py +0 -0
  207. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_log_facade.py +0 -0
  208. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_logging_config_keys.py +0 -0
  209. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_logging_sinks.py +0 -0
  210. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_loguru_caplog.py +0 -0
  211. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager.py +0 -0
  212. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
  213. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager_max_history.py +0 -0
  214. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_parameter_aliasing.py +0 -0
  215. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_parser.py +0 -0
  216. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_reasoning_config.py +0 -0
  217. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_session.py +0 -0
  218. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_session_id_threading.py +0 -0
  219. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_sink_masking.py +0 -0
  220. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/__init__.py +0 -0
  221. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_base.py +0 -0
  222. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_chat_loop_hook.py +0 -0
  223. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
  224. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_loop_severity.py +0 -0
  225. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_required_args_aliases.py +0 -0
  226. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/__init__.py +0 -0
  227. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/__init__.py +0 -0
  228. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_base.py +0 -0
  229. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
  230. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_errors.py +0 -0
  231. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_fallback.py +0 -0
  232. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
  233. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
  234. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_matrix.py +0 -0
  235. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_minimax.py +0 -0
  236. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
  237. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
  238. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_registry.py +0 -0
  239. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_glm_async.py +0 -0
  240. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_inspector.py +0 -0
  241. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_parallel_tool_call_ids.py +0 -0
  242. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml.py +0 -0
  243. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml_autoroute.py +0 -0
  244. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml_usage.py +0 -0
  245. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_orchestrator_capabilities.py +0 -0
  246. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_provider_channel_binding.py +0 -0
  247. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_provider_severity.py +0 -0
  248. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_qwen_async.py +0 -0
  249. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_reasoning_effort.py +0 -0
  250. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_retry_classifier.py +0 -0
  251. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_retry_integration.py +0 -0
  252. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_retrying_provider.py +0 -0
  253. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_logging_policy_gates.py +0 -0
  254. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_loguru_private_shape.py +0 -0
  255. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/services/test_boundary.py +0 -0
  256. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/services/test_interaction_sink.py +0 -0
  257. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/services/test_messaging_inbox.py +0 -0
  258. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_cli.py +0 -0
  259. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_cli_callbacks.py +0 -0
  260. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_context.py +0 -0
  261. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_env_manager.py +0 -0
  262. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_logging_config.py +0 -0
  263. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_notes.py +0 -0
  264. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_process_manager.py +0 -0
  265. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_tasks.py +0 -0
  266. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/__init__.py +0 -0
  267. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_agent_notes.py +0 -0
  268. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_background_process_tool.py +0 -0
  269. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_bash_tool.py +0 -0
  270. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_definition_discovery.py +0 -0
  271. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_di_injection.py +0 -0
  272. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_file_editor_hardening.py +0 -0
  273. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_file_editor_size_limit.py +0 -0
  274. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_impl.py +0 -0
  275. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_impl_coverage.py +0 -0
  276. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_mcp.py +0 -0
  277. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_mcp_client.py +0 -0
  278. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_note_tool.py +0 -0
  279. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_notes_api.py +0 -0
  280. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_path_security.py +0 -0
  281. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_api.py +0 -0
  282. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_github.py +0 -0
  283. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_loading.py +0 -0
  284. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_manager.py +0 -0
  285. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_status.py +0 -0
  286. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_registry.py +0 -0
  287. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_registry_coverage.py +0 -0
  288. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_result.py +0 -0
  289. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_schemas.py +0 -0
  290. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_search_codebase.py +0 -0
  291. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_search_hardening.py +0 -0
  292. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_skill_tool.py +0 -0
  293. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_task_tool.py +0 -0
  294. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_tool_severity.py +0 -0
  295. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_unexpected_args.py +0 -0
  296. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_utils.py +0 -0
  297. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_web.py +0 -0
  298. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/__init__.py +0 -0
  299. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_agent_list_screen.py +0 -0
  300. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_do_clear_coordination.py +0 -0
  301. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_follow_mode.py +0 -0
  302. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_help_screen.py +0 -0
  303. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_input_box.py +0 -0
  304. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_keybindings.py +0 -0
  305. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_multi_select_screen.py +0 -0
  306. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_multiline_input.py +0 -0
  307. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_paste.py +0 -0
  308. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_peer_message_turn.py +0 -0
  309. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_pending_compact_consumer.py +0 -0
  310. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_pull_delivery.py +0 -0
  311. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_resume.py +0 -0
  312. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_status_bar_badges.py +0 -0
  313. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_themes.py +0 -0
  314. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_turn_consumer.py +0 -0
  315. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_widgets.py +0 -0
  316. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_confirm_screen.py +0 -0
  317. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_diff_preview.py +0 -0
  318. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_plugin_autoenable.py +0 -0
  319. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_provider_switch.py +0 -0
  320. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_run_tui_startup.py +0 -0
  321. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_assistant_sanitization.py +0 -0
  322. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_chat_loop.py +0 -0
  323. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_agent.py +0 -0
  324. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_effort.py +0 -0
  325. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_logging.py +0 -0
  326. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_plugin.py +0 -0
  327. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_provider.py +0 -0
  328. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_skill.py +0 -0
  329. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_helpers.py +0 -0
  330. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_temporal_command.py +0 -0
  331. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_widgets.py +0 -0
  332. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_ui.py +0 -0
  333. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_ui_coverage.py +0 -0
  334. {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_ui_verbose.py +0 -0
@@ -87,6 +87,7 @@ ayder-cli/
87
87
  │ ├── providers/ # LLM provider implementations
88
88
  │ │ ├── base.py # AIProvider protocol, NormalizedStreamChunk
89
89
  │ │ ├── orchestrator.py # Factory: create(config) → provider
90
+ │ │ ├── evren.py # evren gateway terms-of-use acceptance (/model)
90
91
  │ │ └── impl/ # Individual provider drivers
91
92
  │ │ ├── openai.py # OpenAIProvider (base for most drivers)
92
93
  │ │ ├── ollama.py # OllamaProvider (native + XML fallback)
@@ -385,6 +386,22 @@ uv run poe test-cov # With coverage report
385
386
  - **Coverage**: Maintain existing coverage levels
386
387
  - **Timeout**: Use `--timeout=30` to prevent hanging tests
387
388
 
389
+ ### Test Definitions
390
+
391
+ Plans and reviews use these values.
392
+
393
+ - **Kinds**
394
+ - `baseline regression` — fails on the parent commit for a named reason, passes on the fix.
395
+ - `new interface` — fails at its assertion once a minimal callable version exists (never on an `ImportError`).
396
+ - `preservation` — passes before and after the change; its assertion depends on the behaviour it preserves.
397
+ - **Layers**
398
+ - `unit` — a function or class, without a Textual app.
399
+ - `tui` — a Textual app driven under `App.run_test()`.
400
+ - **Environment**
401
+ - `local-faked` — the only one. No network: HTTP is faked with `httpx.MockTransport` passed to the SDK client; other I/O uses `unittest.mock`. No test reaches a real provider.
402
+
403
+ The gate procedure lives in [docs/GATE.md](docs/GATE.md).
404
+
388
405
  ### Writing Tests
389
406
 
390
407
  ```python
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ayder-cli
3
- Version: 2.3.1
3
+ Version: 2.4.1
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
@@ -155,6 +155,8 @@ in the core install.
155
155
 
156
156
  ayder-cli uses a flexible profile-based configuration system. On the first run, it creates a config file at `~/.ayder/config.toml`.
157
157
 
158
+ The generated file includes an `[llm.evren]` profile for the evren gateway (`https://evren-llmapi.ssyz.org.tr/v1`) with an empty `api_key`. Fill in your key and switch to it with `/provider evren`.
159
+
158
160
  **Key Concepts:**
159
161
  - **Profile Name:** A custom named section (e.g., `[llm.my_ollama]`). You can define as many profiles as you want.
160
162
  - **Driver:** The underlying native SDK or adapter used by the profile (`ollama`, `openai`, `anthropic`, `google`, `deepseek`, `qwen`, or `glm`). Each driver guarantees full support for native tool calling and streaming.
@@ -202,7 +204,7 @@ editor = "vim" # Editor for /task-edit command
202
204
  verbose = false # Show file contents after write + LLM debug
203
205
  max_background_processes = 5 # Max concurrent background processes (1-20)
204
206
  max_iterations = 10 # Max agentic iterations per message (1-100)
205
- max_output_tokens = 4096 # Max tokens in LLM response
207
+ max_output_tokens = 32768 # Max tokens in LLM response
206
208
  max_history_messages = 30 # Messages kept in history
207
209
  prompt = "STANDARD" # System prompt tier: MINIMAL, STANDARD, EXTENDED
208
210
  tool_tags = ["core", "metadata"] # Enabled tool tags (see /plugin)
@@ -319,6 +321,10 @@ Once a profile is active, use `/model` in the TUI to swap models on the fly:
319
321
  - **Interactive Picker:** `/model` with no arguments queries the active driver for available models and opens a picker.
320
322
  - **Direct Switch:** `/model <model-name>` immediately switches to that model.
321
323
 
324
+ If the provider rejects the listing request, `/model` shows the server's error.
325
+
326
+ For the evren gateway (`evren-llmapi.ssyz.org.tr`), if your account has not accepted the current terms of use, `/model` shows them and asks you to Approve or Deny; Approve accepts them on the server.
327
+
322
328
  Changes made with `/model` apply to the current session only. To make a model the permanent default, update `model = "..."` in your `config.toml`.
323
329
 
324
330
  ## Usage
@@ -408,12 +414,13 @@ ayder-cli includes a context manager that automatically keeps your conversation
408
414
  - **Real token counts**: Uses Ollama's `prompt_eval_count` and `eval_count` from native API responses instead of estimates. Compaction decisions are based on actual usage, not heuristics.
409
415
  - **Cache monitoring**: Tracks `prompt_eval_duration / prompt_eval_count` ratio to detect KV-cache hits (hot/warm/miss). When the cache is hot, compaction is delayed (threshold raised from 70% to 90%) to avoid invalidating it.
410
416
  - **Auto-detected context length**: On startup, queries `/api/show` to get the model's real context length instead of relying on `num_ctx`. Falls back to `num_ctx` if detection fails.
411
- - **Heuristic compaction**: When the context fills up, oldest message units are replaced with a text summary — no LLM call needed.
417
+ - **Heuristic compaction**: When the context fills up, the oldest half of the message units is replaced with a text summary — no LLM call needed. The newest unit is never folded. The summary persists: every later request reuses the same summary message, so each request extends the previous one until the next compaction. A new compaction folds the previous summary into one capped summary (8,000 characters).
418
+ - **Prefix contract**: Message-level prefix stability holds while the model, tools and `max_history = 0` stay unchanged and the history is append-only. Rewriting history (`/clear`, a compact, loading a session) starts a new generation: the summary is dropped and compaction waits for a fresh token count. This guarantees a stable message prefix; whether the server reuses its cache is up to the server.
412
419
  - **Native SDK**: Uses `ollama.AsyncClient` directly (not the OpenAI compatibility layer) for full timing data, native thinking support, and `keep_alive=-1` to prevent model unloading.
413
420
 
414
421
  **General-purpose manager** (Anthropic, OpenAI, Gemini, and other drivers):
415
422
 
416
- Every message is assigned an importance tier (system > recent user > recent assistant > tool results > old history). When the conversation approaches the token budget, old tool results are compressed (JSON structurally summarized, large text head/tail truncated) and the lowest-priority messages are pruned. Tool call + result pairs are kept as atomic units.
423
+ Every message is assigned an importance tier (system > recent user > recent assistant > tool results > old history). When the conversation approaches the token budget, old tool results are compressed (JSON structurally summarized, large text head/tail truncated) and the lowest-priority messages are pruned. Tool call + result pairs are kept as atomic units. The newest unit is always kept, even when it alone exceeds the budget, so the model always sees the request it is answering.
417
424
 
418
425
  The context manager is configured under `[context_manager]` in `config.toml`:
419
426
 
@@ -539,6 +546,8 @@ ayder-cli includes a built-in task system:
539
546
 
540
547
  Tasks are stored as markdown files in `.ayder/tasks/` using slug-based filenames (e.g., `TASK-001-add-auth-middleware.md`).
541
548
 
549
+ The `task` tool resolves identifiers (a path, filename, ID or slug) only to files inside `.ayder/tasks/`. An identifier that points elsewhere, including through a symlink, is not resolved. If `.ayder/tasks/`, `.ayder/context/` or `.ayder/notes/` itself resolves outside the project, those tools return a security error.
550
+
542
551
  ```
543
552
  > /tasks # Interactive task browser
544
553
  > /task-edit 1 # Open TASK-001 in the in-app editor
@@ -112,6 +112,8 @@ in the core install.
112
112
 
113
113
  ayder-cli uses a flexible profile-based configuration system. On the first run, it creates a config file at `~/.ayder/config.toml`.
114
114
 
115
+ The generated file includes an `[llm.evren]` profile for the evren gateway (`https://evren-llmapi.ssyz.org.tr/v1`) with an empty `api_key`. Fill in your key and switch to it with `/provider evren`.
116
+
115
117
  **Key Concepts:**
116
118
  - **Profile Name:** A custom named section (e.g., `[llm.my_ollama]`). You can define as many profiles as you want.
117
119
  - **Driver:** The underlying native SDK or adapter used by the profile (`ollama`, `openai`, `anthropic`, `google`, `deepseek`, `qwen`, or `glm`). Each driver guarantees full support for native tool calling and streaming.
@@ -159,7 +161,7 @@ editor = "vim" # Editor for /task-edit command
159
161
  verbose = false # Show file contents after write + LLM debug
160
162
  max_background_processes = 5 # Max concurrent background processes (1-20)
161
163
  max_iterations = 10 # Max agentic iterations per message (1-100)
162
- max_output_tokens = 4096 # Max tokens in LLM response
164
+ max_output_tokens = 32768 # Max tokens in LLM response
163
165
  max_history_messages = 30 # Messages kept in history
164
166
  prompt = "STANDARD" # System prompt tier: MINIMAL, STANDARD, EXTENDED
165
167
  tool_tags = ["core", "metadata"] # Enabled tool tags (see /plugin)
@@ -276,6 +278,10 @@ Once a profile is active, use `/model` in the TUI to swap models on the fly:
276
278
  - **Interactive Picker:** `/model` with no arguments queries the active driver for available models and opens a picker.
277
279
  - **Direct Switch:** `/model <model-name>` immediately switches to that model.
278
280
 
281
+ If the provider rejects the listing request, `/model` shows the server's error.
282
+
283
+ For the evren gateway (`evren-llmapi.ssyz.org.tr`), if your account has not accepted the current terms of use, `/model` shows them and asks you to Approve or Deny; Approve accepts them on the server.
284
+
279
285
  Changes made with `/model` apply to the current session only. To make a model the permanent default, update `model = "..."` in your `config.toml`.
280
286
 
281
287
  ## Usage
@@ -365,12 +371,13 @@ ayder-cli includes a context manager that automatically keeps your conversation
365
371
  - **Real token counts**: Uses Ollama's `prompt_eval_count` and `eval_count` from native API responses instead of estimates. Compaction decisions are based on actual usage, not heuristics.
366
372
  - **Cache monitoring**: Tracks `prompt_eval_duration / prompt_eval_count` ratio to detect KV-cache hits (hot/warm/miss). When the cache is hot, compaction is delayed (threshold raised from 70% to 90%) to avoid invalidating it.
367
373
  - **Auto-detected context length**: On startup, queries `/api/show` to get the model's real context length instead of relying on `num_ctx`. Falls back to `num_ctx` if detection fails.
368
- - **Heuristic compaction**: When the context fills up, oldest message units are replaced with a text summary — no LLM call needed.
374
+ - **Heuristic compaction**: When the context fills up, the oldest half of the message units is replaced with a text summary — no LLM call needed. The newest unit is never folded. The summary persists: every later request reuses the same summary message, so each request extends the previous one until the next compaction. A new compaction folds the previous summary into one capped summary (8,000 characters).
375
+ - **Prefix contract**: Message-level prefix stability holds while the model, tools and `max_history = 0` stay unchanged and the history is append-only. Rewriting history (`/clear`, a compact, loading a session) starts a new generation: the summary is dropped and compaction waits for a fresh token count. This guarantees a stable message prefix; whether the server reuses its cache is up to the server.
369
376
  - **Native SDK**: Uses `ollama.AsyncClient` directly (not the OpenAI compatibility layer) for full timing data, native thinking support, and `keep_alive=-1` to prevent model unloading.
370
377
 
371
378
  **General-purpose manager** (Anthropic, OpenAI, Gemini, and other drivers):
372
379
 
373
- Every message is assigned an importance tier (system > recent user > recent assistant > tool results > old history). When the conversation approaches the token budget, old tool results are compressed (JSON structurally summarized, large text head/tail truncated) and the lowest-priority messages are pruned. Tool call + result pairs are kept as atomic units.
380
+ Every message is assigned an importance tier (system > recent user > recent assistant > tool results > old history). When the conversation approaches the token budget, old tool results are compressed (JSON structurally summarized, large text head/tail truncated) and the lowest-priority messages are pruned. Tool call + result pairs are kept as atomic units. The newest unit is always kept, even when it alone exceeds the budget, so the model always sees the request it is answering.
374
381
 
375
382
  The context manager is configured under `[context_manager]` in `config.toml`:
376
383
 
@@ -496,6 +503,8 @@ ayder-cli includes a built-in task system:
496
503
 
497
504
  Tasks are stored as markdown files in `.ayder/tasks/` using slug-based filenames (e.g., `TASK-001-add-auth-middleware.md`).
498
505
 
506
+ The `task` tool resolves identifiers (a path, filename, ID or slug) only to files inside `.ayder/tasks/`. An identifier that points elsewhere, including through a symlink, is not resolved. If `.ayder/tasks/`, `.ayder/context/` or `.ayder/notes/` itself resolves outside the project, those tools return a security error.
507
+
499
508
  ```
500
509
  > /tasks # Interactive task browser
501
510
  > /task-edit 1 # Open TASK-001 in the in-app editor
@@ -0,0 +1,28 @@
1
+ # Gate
2
+
3
+ The procedure an agent follows to gate a candidate commit. Follow the steps in order and do
4
+ not interpret the results. Never edit a file, fix a failure, skip a step or create a worktree.
5
+ Test definitions live in [AGENTS.md](../AGENTS.md#test-definitions).
6
+
7
+ Input: the candidate SHA.
8
+
9
+ 1. `git rev-parse HEAD` → expected: exactly the candidate SHA.
10
+ 2. `git status --porcelain` → expected: empty output.
11
+ 3. `uv run poe check-all` → expected: exit code 0. This runs lint, typecheck, the logging
12
+ gates and the test suite, and stops at the first failing task.
13
+ 4. Copy the final pytest summary line (`=== N passed, M skipped in …s ===`) verbatim.
14
+
15
+ Last measured: `2606 passed, 2 skipped` on `fix/max-output-tokens-default` (2026-10-16).
16
+
17
+ ## Report format
18
+
19
+ ```text
20
+ SHA: <candidate SHA>
21
+ 1 git rev-parse HEAD: <output> (match|MISMATCH)
22
+ 2 git status --porcelain: <empty|output>
23
+ 3 uv run poe check-all: exit <code>
24
+ 4 summary: <pytest summary line verbatim>
25
+ GATE COMPLETE | GATE FAILED at step <n>
26
+ ```
27
+
28
+ `GATE COMPLETE` only when steps 1–3 meet their expected results.
@@ -220,7 +220,7 @@ cli.py:main()
220
220
  | `tui/adapter.py` | Adapter glue between `AyderApp` and `ChatLoop` | callback wiring helpers |
221
221
  | `tui/commands.py` | Slash command handlers | `COMMAND_MAP`, `handle_*()` |
222
222
  | `tui/widgets.py` | Custom widgets | `ChatView`, `ToolPanel`, `CLIInputBar`, `StatusBar`, `AutoCompleteInput` |
223
- | `tui/screens.py` | Modal screens | `AgentListScreen`, `CLIConfirmScreen`, `CLIHelpScreen`, `CLIMultiSelectScreen`, `CLIPermissionScreen`, `CLISafeModeScreen`, `CLISelectScreen`, `TaskEditScreen` |
223
+ | `tui/screens.py` | Modal screens | `AgentListScreen`, `CLIConfirmScreen`, `CLIHelpScreen`, `CLIMultiSelectScreen`, `CLIPermissionScreen`, `CLISafeModeScreen`, `CLISelectScreen`, `EvrenTermsScreen`, `TaskEditScreen` |
224
224
  | `tui/parser.py` | TUI-specific parsing | `content_processor()` |
225
225
  | `tui/helpers.py` | UI helpers | `create_tui_banner()` |
226
226
  | `tui/keybindings.py` | Keybinding declarations | `BINDINGS` |
@@ -242,6 +242,7 @@ All LLM provider implementations live here. Previously some sat under `services/
242
242
  | `providers/__init__.py` | Re-exports | `AIProvider`, `NormalizedStreamChunk`, `ToolCallDef`, `provider_orchestrator` |
243
243
  | `providers/base.py` | Provider protocol + shared DTOs | `AIProvider`, `NormalizedStreamChunk`, `ToolCallDef`, `_ToolCall`, `_FunctionCall` |
244
244
  | `providers/orchestrator.py` | Driver-keyed provider factory | `ProviderOrchestrator`, `provider_orchestrator` |
245
+ | `providers/evren.py` | evren gateway terms-of-use acceptance (used by `/model`) | `EVREN_HOST`, `EvrenTerms`, `is_evren_terms_error`, `fetch_terms`, `accept_terms` |
245
246
  | `providers/impl/ollama.py` | Native Ollama provider (ollama SDK) | `OllamaProvider` |
246
247
  | `providers/impl/ollama_inspector.py` | Ollama model introspection | `OllamaInspector`, `ModelInfo`, `RuntimeState` |
247
248
  | `providers/impl/openai.py` | OpenAI / OpenAI-compatible backend | `OpenAIProvider` |
@@ -604,9 +605,11 @@ previously-split `AgentLoopBase` and `LoopConfig` were merged back in):
604
605
  - Routes *every* LLM response through the injected `ContextManagerProtocol` (`prepare_messages()` / `update_from_response()`); tool outputs are truncated at insertion time via `truncate_tool_result()` so the KV-cache prefix remains stable
605
606
 
606
607
  Key features:
607
- - Auto-approved tools run in parallel (`asyncio.gather`); confirmation-required tools run sequentially
608
+ - Auto-approved tools run in parallel (`asyncio.as_completed`); confirmation-required tools run sequentially
608
609
  - Confirmation gate applies to **both** OpenAI-format and XML/JSON custom tool calls
609
- - `ExecutionPolicy.execute_with_registry()` is the single execution entry (validate → permission → execute)
610
+ - `ExecutionPolicy.execute_with_registry()` is the single execution entry (object check → `canonicalize_arguments()` → validate → permission → execute). Canonicalization applies aliases and `"123"`/`"true"`/`"false"` coercion; the loop's required-argument gate uses the same function
611
+ - Argument integrity: `_parse_tool_arguments()` reports `ok`, `repaired` or `invalid`. Non-object arguments never run. Arguments repaired after being cut off (output token limit) run only for tools registered explicitly as `r`; other tools get an error asking the model to resend in smaller calls
612
+ - Cancellation: each batch owns a `_ToolBatch`. Workers refuse to start once the batch is cancelled and record their own result and `tool_call` event. When a batch ends early (Ctrl+C or a raising callback), a `finally` appends exactly one tool message per unanswered call: its result, an "interrupted, may have partially applied" notice, or a "cancelled before it ran" notice. Running workers are not awaited, so Ctrl+C returns at once
610
613
  - Token usage tracking and iteration limiting
611
614
  - Pre-iteration hook (`pre_iteration_hook`) used by the agent system to inject summaries
612
615
  - Graceful cancellation via `is_cancelled()`
@@ -201,11 +201,20 @@ think = true # default; set false if the selected model r
201
201
  # api_key = "sk-..." # your key (or "ollama"/"none" for local servers)
202
202
  # model = "gpt-5.5"
203
203
  # num_ctx = 65536 # sizes the context-manager budget (default 65536); NOT sent to the API
204
- # max_output_tokens = 8192 # -> max_tokens / max_completion_tokens (default 4096); keep WELL below num_ctx
204
+ # max_output_tokens = 8192 # -> max_tokens / max_completion_tokens (default 32768); keep WELL below num_ctx
205
205
  # stop_sequences = [] # -> stop (default [])
206
206
  # reasoning_effort = "medium" # choose a value supported by the selected model
207
207
  # # temperature / think are NOT applied by the openai driver.
208
208
 
209
+ # evren gateway (OpenAI-compatible). A fresh config.toml ships this profile with
210
+ # an empty api_key. The first `/model` asks you to accept evren's terms of use.
211
+ # [llm.evren]
212
+ # driver = "openai"
213
+ # base_url = "https://evren-llmapi.ssyz.org.tr/v1"
214
+ # api_key = "" # your evren key
215
+ # model = "deepseek-v4-flash"
216
+ # num_ctx = 131072
217
+
209
218
  # =============================================================================
210
219
  # Agents
211
220
  # =============================================================================
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ayder-cli"
7
- version = "2.3.1"
7
+ version = "2.4.1"
8
8
  description = "AI agent for any LLMs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -24,7 +24,7 @@ DEFAULT_BASELINE = REPO / "scripts" / "exception_baseline.txt"
24
24
 
25
25
  # Frozen census (§C10). A different number means the gate is looking at the
26
26
  # wrong tree, or the census moved without the plan being updated.
27
- EXPECTED_BROAD = 138
27
+ EXPECTED_BROAD = 137
28
28
  BRANCHING = (ast.If, ast.Try, ast.For, ast.While, ast.With, ast.Match)
29
29
  TERMINAL = (ast.Return, ast.Break, ast.Continue, ast.Pass)
30
30
 
@@ -203,13 +203,13 @@ FROZEN_CHAIN_SITES: dict[tuple[str, str, str, str], tuple[str, int]] = {
203
203
  ("log.py", "emit_event", "bind", "**"): ("Name(id='fields', ctx=Load())", 1),
204
204
  }
205
205
 
206
- # The 100 `exception=True` inputs are homogeneous, so they are governed as one
207
- # package-wide shape/count rule rather than 100 site identities - their per-site
206
+ # The 101 `exception=True` inputs are homogeneous, so they are governed as one
207
+ # package-wide shape/count rule rather than 101 site identities - their per-site
208
208
  # discipline is C10's.
209
- FROZEN_EXCEPTION_TRUE = 100
209
+ FROZEN_EXCEPTION_TRUE = 101
210
210
 
211
211
  # Live chain inputs, counted per method. Printed on every run.
212
- CHAIN_TALLY: dict[str, int] = {"bind": 6, "opt": 104, "patch": 0}
212
+ CHAIN_TALLY: dict[str, int] = {"bind": 6, "opt": 105, "patch": 0}
213
213
 
214
214
  # The reviewed composition of THE COMMITTED baseline, frozen by the Phase 5
215
215
  # census: 252 retained non-path + 22 paths + 2 deferred interpolated rows, plus
@@ -158,19 +158,40 @@ class ExecutionPolicy:
158
158
  *,
159
159
  pre_approved: bool = False,
160
160
  ) -> ExecutionResult:
161
- """Single shared execution path: validate → check permission → execute.
161
+ """Single shared execution path: canonicalize → validate → permission → execute.
162
162
 
163
163
  Both CLI and TUI route through this method so registry.execute is never
164
164
  called directly from interface-specific code.
165
165
 
166
+ Order:
167
+ 1. Arguments that are not a JSON object are a validation error.
168
+ 2. ``canonicalize_arguments``: aliases, integer and boolean coercion.
169
+ 3. ``ValidationAuthority`` on the canonical arguments.
170
+ 4. Permission check (unless pre-approved), then ``registry.execute``
171
+ with the canonical arguments.
172
+
166
173
  Args:
167
174
  pre_approved: If True, skip the permission check (user already
168
175
  confirmed via the confirmation dialog).
169
176
  """
170
177
  from ayder_cli.application.validation import ValidationAuthority
178
+ from ayder_cli.tools.normalization import canonicalize_arguments
179
+
180
+ if not isinstance(request.arguments, dict):
181
+ return ExecutionResult(
182
+ success=False,
183
+ error=ValidationError(
184
+ tool_name=request.name,
185
+ message=(
186
+ "arguments must be a JSON object, got "
187
+ f"{type(request.arguments).__name__}"
188
+ ),
189
+ ),
190
+ )
191
+ canonical = canonicalize_arguments(request.name, request.arguments)
171
192
 
172
193
  authority = ValidationAuthority()
173
- v_request = ToolRequest(name=request.name, arguments=request.arguments)
194
+ v_request = ToolRequest(name=request.name, arguments=canonical)
174
195
  valid, err = authority.validate(v_request)
175
196
  if not valid:
176
197
  return ExecutionResult(success=False, error=err)
@@ -180,7 +201,7 @@ class ExecutionPolicy:
180
201
  if perm_err is not None:
181
202
  return ExecutionResult(success=False, error=perm_err)
182
203
 
183
- raw = registry.execute(request.name, request.arguments)
204
+ raw = registry.execute(request.name, v_request.arguments)
184
205
  if isinstance(raw, ToolError):
185
206
  # A tool that ran and failed is NOT a successful execution.
186
207
  return ExecutionResult(success=False, was_confirmed=pre_approved, error=raw)
@@ -101,6 +101,12 @@ class SchemaValidator:
101
101
  field=param_name,
102
102
  message=f"'{param_name}' must be integer, got {type(value).__name__}",
103
103
  )
104
+ if expected_type == "boolean" and not isinstance(value, bool):
105
+ return False, ValidationError(
106
+ tool_name=request.name,
107
+ field=param_name,
108
+ message=f"'{param_name}' must be boolean, got {type(value).__name__}",
109
+ )
104
110
  if expected_type == "string" and not isinstance(value, str):
105
111
  return False, ValidationError(
106
112
  tool_name=request.name,
@@ -45,6 +45,16 @@ DEFAULTS: Dict[str, Any] = {
45
45
  "model": "gemini-3-flash",
46
46
  "num_ctx": 65536,
47
47
  },
48
+ # Extra [llm.<name>] profiles written into a freshly created config.toml.
49
+ "extra_llm_profiles": {
50
+ "evren": {
51
+ "driver": "openai",
52
+ "base_url": "https://evren-llmapi.ssyz.org.tr/v1",
53
+ "api_key": "",
54
+ "model": "deepseek-v4-flash",
55
+ "num_ctx": 131072,
56
+ },
57
+ },
48
58
  "editor": "vim",
49
59
  "verbose": False,
50
60
  "logging": {
@@ -342,7 +352,7 @@ class Config(BaseModel):
342
352
  max_background_processes: int = Field(default=5)
343
353
  # Local Unix-socket inbox that accepts prompts from peer processes.
344
354
  messaging_enabled: bool = Field(default=True)
345
- max_output_tokens: int = Field(default=4096)
355
+ max_output_tokens: int = Field(default=32768)
346
356
  # 0 = unlimited: let the context manager's token budget + compaction be the
347
357
  # bound. The old fixed 30 predates large-context models and thrashes a tiny
348
358
  # history window on a big context.
@@ -74,6 +74,8 @@ def render_v2_config(
74
74
  section = dict(defaults.get(provider, {}))
75
75
  section.setdefault("driver", _DRIVER_BY_PROVIDER[provider])
76
76
  llm_profiles[provider] = section
77
+ for name, section in defaults.get("extra_llm_profiles", {}).items():
78
+ llm_profiles[name] = dict(section)
77
79
  if llm_overrides:
78
80
  for name, section in llm_overrides.items():
79
81
  merged = dict(llm_profiles.get(name, {}))
@@ -44,6 +44,39 @@ class ProjectContext:
44
44
 
45
45
  return target_path
46
46
 
47
+ def harness_dir(self, *parts: str) -> Path:
48
+ """Return ``<root>/.ayder/<parts>``, refusing a location outside the root.
49
+
50
+ ``validate_path`` resolves symlinks, so a ``.ayder/<parts>`` symlink that
51
+ points outside the project raises instead of redirecting writes.
52
+ The directory is not created.
53
+
54
+ Raises:
55
+ ValueError: If the resolved directory is outside the project root.
56
+ """
57
+ return self.validate_path(str(Path(".ayder", *parts)))
58
+
59
+ @staticmethod
60
+ def within(directory: Path, path: Path | str) -> Path:
61
+ """Return ``path`` unchanged if it resolves inside ``directory``.
62
+
63
+ The resolved form is only checked, never returned, so names and
64
+ relative paths stay stable when an in-directory symlink is followed.
65
+ ``os.path.realpath`` is used rather than ``Path.resolve``, which also
66
+ stats the final target. Resolving still lstats and reads links along
67
+ the path, including outside components; what it never does is read or
68
+ follow-stat the target's content, so a refused entry stays unread.
69
+
70
+ Raises:
71
+ ValueError: If ``path`` resolves outside ``directory``.
72
+ """
73
+ resolved = Path(os.path.realpath(path))
74
+ if not resolved.is_relative_to(Path(os.path.realpath(directory))):
75
+ raise ValueError(
76
+ f"Security Alert: Path '{resolved}' is outside '{directory}'"
77
+ )
78
+ return Path(path)
79
+
47
80
  def to_relative(self, absolute_path: Path) -> str:
48
81
  """Convert an absolute path to relative path for LLM output.
49
82
 
@@ -371,39 +371,45 @@ class DefaultContextManager:
371
371
  overhead = effective_system_tokens + effective_schema_tokens
372
372
  available = self._available_budget(overhead)
373
373
 
374
- if available <= 0:
375
- # Overhead ate the whole budget: every history message is dropped.
376
- kept = 1 if system_msg else 0
377
- self._emit_trim(len(self._messages), kept, "budget_exhausted")
378
- return [system_msg] if system_msg else []
379
-
380
374
  start_idx = 1 if system_msg else 0
381
375
  history_msgs = self._messages[start_idx:]
382
376
 
383
377
  units = self._group_into_units(history_msgs)
384
378
 
385
379
  result: List[Dict[str, Any]] = []
386
- used = 0
387
- messages_count = 0
388
- reason: str | None = None
389
- for unit in reversed(units):
390
- if max_history > 0 and messages_count + len(unit) > max_history:
391
- reason = "max_history"
392
- break
393
-
394
- cost = self._counter.count_messages(unit)
395
- if used + cost > available:
396
- reason = "token_budget"
397
- break
398
-
399
- result = unit + result
400
- used += cost
401
- messages_count += len(unit)
402
-
403
- # Emitted BEFORE the truncation marker below: that marker ADDS a
404
- # message, and counting after it would under-report `dropped` by one.
405
- if reason is not None:
406
- self._emit_trim(len(history_msgs), len(result), reason)
380
+ if available <= 0:
381
+ # Overhead ate the whole budget: every history message is dropped
382
+ # except the newest unit, the request the model must see (F8).
383
+ if units:
384
+ result = list(units[-1])
385
+ kept = (1 if system_msg else 0) + len(result)
386
+ self._emit_trim(len(self._messages), kept, "budget_exhausted")
387
+ else:
388
+ used = 0
389
+ messages_count = 0
390
+ reason: str | None = None
391
+ for unit in reversed(units):
392
+ # The newest unit is always kept, even when it alone exceeds
393
+ # the budget or max_history: dropping it would send a request
394
+ # the model never saw (F8). If it truly exceeds the model's
395
+ # window, the provider's error reaches the user.
396
+ if result and max_history > 0 and messages_count + len(unit) > max_history:
397
+ reason = "max_history"
398
+ break
399
+
400
+ cost = self._counter.count_messages(unit)
401
+ if result and used + cost > available:
402
+ reason = "token_budget"
403
+ break
404
+
405
+ result = unit + result
406
+ used += cost
407
+ messages_count += len(unit)
408
+
409
+ # Emitted BEFORE the truncation marker below: that marker ADDS a
410
+ # message, and counting after it would under-report `dropped` by one.
411
+ if reason is not None:
412
+ self._emit_trim(len(history_msgs), len(result), reason)
407
413
 
408
414
  if result and result[0].get("role") != "user":
409
415
  result.insert(