ayder-cli 2.4.0__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.4.0 → ayder_cli-2.4.1}/PKG-INFO +7 -4
  2. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/README.md +6 -3
  3. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/GATE.md +1 -1
  4. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/PROJECT_STRUCTURE.md +4 -2
  5. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/config.toml.example +1 -1
  6. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/pyproject.toml +1 -1
  7. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/application/execution_policy.py +24 -3
  8. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/application/validation.py +6 -0
  9. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/config.py +1 -1
  10. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/context.py +33 -0
  11. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/default_context_manager.py +33 -27
  12. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/ollama_context_manager.py +122 -30
  13. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/loops/chat_loop.py +438 -236
  14. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/claude.py +26 -7
  15. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/gemini.py +26 -22
  16. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/context.py +36 -5
  17. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/note_tool.py +22 -0
  18. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/notes.py +33 -10
  19. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/task_tool.py +29 -1
  20. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/tasks.py +59 -13
  21. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/normalization.py +48 -29
  22. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/app.py +2 -2
  23. ayder_cli-2.4.1/tests/application/test_argument_canonicalization.py +136 -0
  24. ayder_cli-2.4.1/tests/core/test_default_context_newest_unit.py +133 -0
  25. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_event_emission.py +3 -2
  26. ayder_cli-2.4.1/tests/core/test_max_output_tokens_default.py +121 -0
  27. ayder_cli-2.4.1/tests/core/test_ollama_compaction_persistence.py +235 -0
  28. ayder_cli-2.4.1/tests/loops/test_tool_argument_integrity.py +247 -0
  29. ayder_cli-2.4.1/tests/loops/test_tool_batch_cancellation.py +506 -0
  30. ayder_cli-2.4.1/tests/providers/test_claude_stream_tool_calls.py +242 -0
  31. ayder_cli-2.4.1/tests/providers/test_gemini_function_calls.py +179 -0
  32. ayder_cli-2.4.1/tests/tools/test_context_storage_confinement.py +150 -0
  33. ayder_cli-2.4.1/tests/tools/test_notes_confinement.py +129 -0
  34. ayder_cli-2.4.1/tests/tools/test_task_path_confinement.py +145 -0
  35. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/uv.lock +1 -1
  36. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/.github/workflows/ci.yml +0 -0
  37. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/.github/workflows/python-package.yml +0 -0
  38. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/.gitignore +0 -0
  39. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/.python-version +0 -0
  40. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/AGENTS.md +0 -0
  41. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/LICENSE +0 -0
  42. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/README_AGENT_WORKFLOW.md +0 -0
  43. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/README_OLLAMA.md +0 -0
  44. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/cc.png +0 -0
  45. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/superpowers/plans/2026-06-26-file-editor-improvements.md +0 -0
  46. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/superpowers/plans/2026-06-26-session-save-resume.md +0 -0
  47. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/superpowers/specs/2026-06-26-file-editor-improvements-design.md +0 -0
  48. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/docs/superpowers/specs/2026-06-26-session-save-resume-design.md +0 -0
  49. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/check_exception_handlers.py +0 -0
  50. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/check_log_arguments.py +0 -0
  51. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/check_logging_imports.py +0 -0
  52. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/exception_baseline.txt +0 -0
  53. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/find_percent_logging.py +0 -0
  54. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/log_argument_baseline.txt +0 -0
  55. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/scripts/logging_gates.py +0 -0
  56. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/__init__.py +0 -0
  57. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/__main__.py +0 -0
  58. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/__init__.py +0 -0
  59. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/callbacks.py +0 -0
  60. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/config.py +0 -0
  61. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/registry.py +0 -0
  62. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/run.py +0 -0
  63. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/runner.py +0 -0
  64. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/tool.py +0 -0
  65. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/agents/worktree.py +0 -0
  66. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/application/README.md +0 -0
  67. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/application/__init__.py +0 -0
  68. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/application/message_contract.py +0 -0
  69. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/application/runtime_factory.py +0 -0
  70. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/cli.py +0 -0
  71. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/cli_callbacks.py +0 -0
  72. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/cli_runner.py +0 -0
  73. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/console.py +0 -0
  74. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/cache_monitor.py +0 -0
  75. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/config_migration.py +0 -0
  76. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/context_manager.py +0 -0
  77. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/context_manager_factory.py +0 -0
  78. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/reasoning.py +0 -0
  79. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/result.py +0 -0
  80. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/core/session.py +0 -0
  81. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/diagnostics.py +0 -0
  82. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/log.py +0 -0
  83. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/logging_config.py +0 -0
  84. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/loops/__init__.py +0 -0
  85. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/masking.py +0 -0
  86. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/parser.py +0 -0
  87. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/process_manager.py +0 -0
  88. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/prompts.py +0 -0
  89. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/__init__.py +0 -0
  90. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/base.py +0 -0
  91. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/evren.py +0 -0
  92. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/deepseek.py +0 -0
  93. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/glm.py +0 -0
  94. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama.py +0 -0
  95. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
  96. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
  97. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
  98. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
  99. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
  100. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
  101. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
  102. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
  103. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
  104. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
  105. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
  106. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/openai.py +0 -0
  107. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/qwen.py +0 -0
  108. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/orchestrator.py +0 -0
  109. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/providers/retry.py +0 -0
  110. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/services/__init__.py +0 -0
  111. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/services/interactions.py +0 -0
  112. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/services/messaging_inbox.py +0 -0
  113. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/themes/__init__.py +0 -0
  114. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/themes/ayder.py +0 -0
  115. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/themes/claude.py +0 -0
  116. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/__init__.py +0 -0
  117. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/__init__.py +0 -0
  118. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/background_process_tool.py +0 -0
  119. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
  120. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
  121. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
  122. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
  123. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
  124. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/search.py +0 -0
  125. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
  126. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/shell.py +0 -0
  127. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
  128. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/skill.py +0 -0
  129. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/skill_definitions.py +0 -0
  130. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/task_definitions.py +0 -0
  131. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
  132. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
  133. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/web.py +0 -0
  134. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
  135. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/definition.py +0 -0
  136. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/execution.py +0 -0
  137. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/hooks.py +0 -0
  138. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/mcp.py +0 -0
  139. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/mcp_client.py +0 -0
  140. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_api.py +0 -0
  141. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_github.py +0 -0
  142. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_manager.py +0 -0
  143. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_status.py +0 -0
  144. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/registry.py +0 -0
  145. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/schemas.py +0 -0
  146. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tools/utils.py +0 -0
  147. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/__init__.py +0 -0
  148. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/adapter.py +0 -0
  149. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/commands.py +0 -0
  150. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/helpers.py +0 -0
  151. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/keybindings.py +0 -0
  152. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/parser.py +0 -0
  153. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/rendering.py +0 -0
  154. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/screens.py +0 -0
  155. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/theme_manager.py +0 -0
  156. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/types.py +0 -0
  157. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/tui/widgets.py +0 -0
  158. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/ui.py +0 -0
  159. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/src/ayder_cli/version.py +0 -0
  160. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/COVERAGE.md +0 -0
  161. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/__init__.py +0 -0
  162. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/__init__.py +0 -0
  163. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_agent_consolidated.py +0 -0
  164. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_agent_severity.py +0 -0
  165. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_agent_tools.py +0 -0
  166. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_callbacks.py +0 -0
  167. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_config.py +0 -0
  168. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_integration.py +0 -0
  169. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_queue.py +0 -0
  170. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_registry.py +0 -0
  171. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_run.py +0 -0
  172. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_runner.py +0 -0
  173. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_runner_provider_unavailable.py +0 -0
  174. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_tool.py +0 -0
  175. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_worktree_helpers.py +0 -0
  176. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/agents/test_worktree_isolation.py +0 -0
  177. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/application/test_execution_result_truthfulness.py +0 -0
  178. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/application/test_message_contract.py +0 -0
  179. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/application/test_runtime_factory.py +0 -0
  180. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/client/test_main.py +0 -0
  181. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/conftest.py +0 -0
  182. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/convergence/__init__.py +0 -0
  183. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/convergence/test_execution_policy_parity.py +0 -0
  184. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/convergence/test_runtime_wiring.py +0 -0
  185. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/convergence/test_validation_path.py +0 -0
  186. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_asyncio_bridge.py +0 -0
  187. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_cache_monitor.py +0 -0
  188. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_config.py +0 -0
  189. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_config_coverage.py +0 -0
  190. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_config_migration.py +0 -0
  191. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_config_provider_logging.py +0 -0
  192. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_config_retry.py +0 -0
  193. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_config_v2.py +0 -0
  194. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_context_manager_factory.py +0 -0
  195. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_default_config_evren.py +0 -0
  196. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_default_context_manager.py +0 -0
  197. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_default_context_manager_should_compact.py +0 -0
  198. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_event_stream.py +0 -0
  199. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_exception_guarantee.py +0 -0
  200. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_exception_hooks.py +0 -0
  201. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_log_facade.py +0 -0
  202. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_logging_config_keys.py +0 -0
  203. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_logging_sinks.py +0 -0
  204. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_loguru_caplog.py +0 -0
  205. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager.py +0 -0
  206. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
  207. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager_max_history.py +0 -0
  208. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_parameter_aliasing.py +0 -0
  209. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_parser.py +0 -0
  210. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_reasoning_config.py +0 -0
  211. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_session.py +0 -0
  212. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_session_id_threading.py +0 -0
  213. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/core/test_sink_masking.py +0 -0
  214. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/loops/__init__.py +0 -0
  215. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/loops/test_base.py +0 -0
  216. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/loops/test_chat_loop_hook.py +0 -0
  217. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
  218. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/loops/test_loop_severity.py +0 -0
  219. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/loops/test_required_args_aliases.py +0 -0
  220. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/__init__.py +0 -0
  221. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/__init__.py +0 -0
  222. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_base.py +0 -0
  223. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
  224. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_errors.py +0 -0
  225. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_fallback.py +0 -0
  226. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
  227. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
  228. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_matrix.py +0 -0
  229. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_minimax.py +0 -0
  230. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
  231. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
  232. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_registry.py +0 -0
  233. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_evren.py +0 -0
  234. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_glm_async.py +0 -0
  235. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_ollama_inspector.py +0 -0
  236. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_ollama_native.py +0 -0
  237. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_ollama_parallel_tool_call_ids.py +0 -0
  238. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml.py +0 -0
  239. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml_autoroute.py +0 -0
  240. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml_usage.py +0 -0
  241. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_openai_list_models.py +0 -0
  242. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_orchestrator_capabilities.py +0 -0
  243. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_provider_channel_binding.py +0 -0
  244. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_provider_severity.py +0 -0
  245. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_qwen_async.py +0 -0
  246. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_reasoning_effort.py +0 -0
  247. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_retry_classifier.py +0 -0
  248. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_retry_integration.py +0 -0
  249. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/providers/test_retrying_provider.py +0 -0
  250. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/scripts/test_exception_gate.py +0 -0
  251. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/scripts/test_log_argument_gate.py +0 -0
  252. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/scripts/test_logging_policy_gates.py +0 -0
  253. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/scripts/test_loguru_private_shape.py +0 -0
  254. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/services/test_boundary.py +0 -0
  255. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/services/test_interaction_sink.py +0 -0
  256. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/services/test_messaging_inbox.py +0 -0
  257. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_cli.py +0 -0
  258. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_cli_callbacks.py +0 -0
  259. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_context.py +0 -0
  260. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_env_manager.py +0 -0
  261. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_logging_config.py +0 -0
  262. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_notes.py +0 -0
  263. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_process_manager.py +0 -0
  264. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/test_tasks.py +0 -0
  265. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/__init__.py +0 -0
  266. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_agent_notes.py +0 -0
  267. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_background_process_tool.py +0 -0
  268. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_bash_tool.py +0 -0
  269. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_definition_discovery.py +0 -0
  270. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_di_injection.py +0 -0
  271. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_file_editor_hardening.py +0 -0
  272. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_file_editor_size_limit.py +0 -0
  273. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_impl.py +0 -0
  274. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_impl_coverage.py +0 -0
  275. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_mcp.py +0 -0
  276. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_mcp_client.py +0 -0
  277. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_note_tool.py +0 -0
  278. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_notes_api.py +0 -0
  279. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_path_security.py +0 -0
  280. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_plugin_api.py +0 -0
  281. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_plugin_github.py +0 -0
  282. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_plugin_loading.py +0 -0
  283. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_plugin_manager.py +0 -0
  284. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_plugin_status.py +0 -0
  285. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_registry.py +0 -0
  286. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_registry_coverage.py +0 -0
  287. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_result.py +0 -0
  288. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_schemas.py +0 -0
  289. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_search_codebase.py +0 -0
  290. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_search_hardening.py +0 -0
  291. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_skill_tool.py +0 -0
  292. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_task_tool.py +0 -0
  293. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_tool_severity.py +0 -0
  294. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_unexpected_args.py +0 -0
  295. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_utils.py +0 -0
  296. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tools/test_web.py +0 -0
  297. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/__init__.py +0 -0
  298. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_agent_list_screen.py +0 -0
  299. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_do_clear_coordination.py +0 -0
  300. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_evren_terms.py +0 -0
  301. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_follow_mode.py +0 -0
  302. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_help_screen.py +0 -0
  303. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_input_box.py +0 -0
  304. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_keybindings.py +0 -0
  305. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_multi_select_screen.py +0 -0
  306. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_multiline_input.py +0 -0
  307. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_paste.py +0 -0
  308. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_peer_message_turn.py +0 -0
  309. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_pending_compact_consumer.py +0 -0
  310. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_pull_delivery.py +0 -0
  311. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_resume.py +0 -0
  312. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_status_bar_badges.py +0 -0
  313. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_themes.py +0 -0
  314. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_turn_consumer.py +0 -0
  315. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/tui/test_widgets.py +0 -0
  316. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_confirm_screen.py +0 -0
  317. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_diff_preview.py +0 -0
  318. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_plugin_autoenable.py +0 -0
  319. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_provider_switch.py +0 -0
  320. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_run_tui_startup.py +0 -0
  321. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_assistant_sanitization.py +0 -0
  322. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_chat_loop.py +0 -0
  323. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_agent.py +0 -0
  324. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_effort.py +0 -0
  325. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_logging.py +0 -0
  326. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_plugin.py +0 -0
  327. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_provider.py +0 -0
  328. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_skill.py +0 -0
  329. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_helpers.py +0 -0
  330. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_temporal_command.py +0 -0
  331. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_tui_widgets.py +0 -0
  332. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_ui.py +0 -0
  333. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_ui_coverage.py +0 -0
  334. {ayder_cli-2.4.0 → ayder_cli-2.4.1}/tests/ui/test_ui_verbose.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ayder-cli
3
- Version: 2.4.0
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
@@ -204,7 +204,7 @@ editor = "vim" # Editor for /task-edit command
204
204
  verbose = false # Show file contents after write + LLM debug
205
205
  max_background_processes = 5 # Max concurrent background processes (1-20)
206
206
  max_iterations = 10 # Max agentic iterations per message (1-100)
207
- max_output_tokens = 4096 # Max tokens in LLM response
207
+ max_output_tokens = 32768 # Max tokens in LLM response
208
208
  max_history_messages = 30 # Messages kept in history
209
209
  prompt = "STANDARD" # System prompt tier: MINIMAL, STANDARD, EXTENDED
210
210
  tool_tags = ["core", "metadata"] # Enabled tool tags (see /plugin)
@@ -414,12 +414,13 @@ ayder-cli includes a context manager that automatically keeps your conversation
414
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.
415
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.
416
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.
417
- - **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.
418
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.
419
420
 
420
421
  **General-purpose manager** (Anthropic, OpenAI, Gemini, and other drivers):
421
422
 
422
- 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.
423
424
 
424
425
  The context manager is configured under `[context_manager]` in `config.toml`:
425
426
 
@@ -545,6 +546,8 @@ ayder-cli includes a built-in task system:
545
546
 
546
547
  Tasks are stored as markdown files in `.ayder/tasks/` using slug-based filenames (e.g., `TASK-001-add-auth-middleware.md`).
547
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
+
548
551
  ```
549
552
  > /tasks # Interactive task browser
550
553
  > /task-edit 1 # Open TASK-001 in the in-app editor
@@ -161,7 +161,7 @@ editor = "vim" # Editor for /task-edit command
161
161
  verbose = false # Show file contents after write + LLM debug
162
162
  max_background_processes = 5 # Max concurrent background processes (1-20)
163
163
  max_iterations = 10 # Max agentic iterations per message (1-100)
164
- max_output_tokens = 4096 # Max tokens in LLM response
164
+ max_output_tokens = 32768 # Max tokens in LLM response
165
165
  max_history_messages = 30 # Messages kept in history
166
166
  prompt = "STANDARD" # System prompt tier: MINIMAL, STANDARD, EXTENDED
167
167
  tool_tags = ["core", "metadata"] # Enabled tool tags (see /plugin)
@@ -371,12 +371,13 @@ ayder-cli includes a context manager that automatically keeps your conversation
371
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.
372
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.
373
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.
374
- - **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.
375
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.
376
377
 
377
378
  **General-purpose manager** (Anthropic, OpenAI, Gemini, and other drivers):
378
379
 
379
- 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.
380
381
 
381
382
  The context manager is configured under `[context_manager]` in `config.toml`:
382
383
 
@@ -502,6 +503,8 @@ ayder-cli includes a built-in task system:
502
503
 
503
504
  Tasks are stored as markdown files in `.ayder/tasks/` using slug-based filenames (e.g., `TASK-001-add-auth-middleware.md`).
504
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
+
505
508
  ```
506
509
  > /tasks # Interactive task browser
507
510
  > /task-edit 1 # Open TASK-001 in the in-app editor
@@ -12,7 +12,7 @@ Input: the candidate SHA.
12
12
  gates and the test suite, and stops at the first failing task.
13
13
  4. Copy the final pytest summary line (`=== N passed, M skipped in …s ===`) verbatim.
14
14
 
15
- Last measured: `2494 passed, 2 skipped` on `feat/evren-terms` with the evren default profile (2026-09-29).
15
+ Last measured: `2606 passed, 2 skipped` on `fix/max-output-tokens-default` (2026-10-16).
16
16
 
17
17
  ## Report format
18
18
 
@@ -605,9 +605,11 @@ previously-split `AgentLoopBase` and `LoopConfig` were merged back in):
605
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
606
606
 
607
607
  Key features:
608
- - 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
609
609
  - Confirmation gate applies to **both** OpenAI-format and XML/JSON custom tool calls
610
- - `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
611
613
  - Token usage tracking and iteration limiting
612
614
  - Pre-iteration hook (`pre_iteration_hook`) used by the agent system to inject summaries
613
615
  - Graceful cancellation via `is_cancelled()`
@@ -201,7 +201,7 @@ 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.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ayder-cli"
7
- version = "2.4.0"
7
+ version = "2.4.1"
8
8
  description = "AI agent for any LLMs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -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,
@@ -352,7 +352,7 @@ class Config(BaseModel):
352
352
  max_background_processes: int = Field(default=5)
353
353
  # Local Unix-socket inbox that accepts prompts from peer processes.
354
354
  messaging_enabled: bool = Field(default=True)
355
- max_output_tokens: int = Field(default=4096)
355
+ max_output_tokens: int = Field(default=32768)
356
356
  # 0 = unlimited: let the context manager's token budget + compaction be the
357
357
  # bound. The old fixed 30 predates large-context models and thrashes a tiny
358
358
  # history window on a big context.
@@ -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(
@@ -23,6 +23,10 @@ from ayder_cli.providers.impl.ollama_inspector import OllamaInspector
23
23
 
24
24
  logger = get_logger("context")
25
25
 
26
+ SUMMARY_HEADER = "[Previous conversation summary]"
27
+ # Cap on the accumulated summary body. Oldest lines are evicted first.
28
+ _SUMMARY_MAX_CHARS = 8000
29
+
26
30
 
27
31
  @dataclass
28
32
  class OllamaContextStats(ContextStats):
@@ -46,6 +50,22 @@ class OllamaContextManager:
46
50
  3. Tool results are truncated at insertion time (immutable after that).
47
51
  4. Compaction is the only mutation — oldest units replaced with a heuristic
48
52
  text summary, never an LLM call.
53
+
54
+ Compaction persists. The caller's history is never mutated; the manager
55
+ remembers the folded message objects and one summary dict, and every later
56
+ call replaces exactly those objects with that same summary. Between two
57
+ compactions or resets, each request's message list therefore extends the
58
+ previous one, provided the model, tools and rendering are unchanged,
59
+ ``max_history`` is 0, and the history is append-only with no sent message
60
+ dict edited in place. That last point is an assumption of this manager's
61
+ history contract (object identity cannot see an in-place edit). The
62
+ system message is exempt: the frozen copy is always sent.
63
+
64
+ A history that is not an identity extension of the previous call's input
65
+ (``/clear``, a pending compact, a session load, a removed skill message)
66
+ is a new generation: the summary is dropped and compaction waits for a
67
+ fresh ``prompt_tokens`` measurement, since the stale one described the old
68
+ history. Every compaction or reset is an intentional prefix change.
49
69
  """
50
70
 
51
71
  def __init__(
@@ -85,6 +105,12 @@ class OllamaContextManager:
85
105
  self._compaction_count: int = 0
86
106
  self._messages_compacted: int = 0
87
107
 
108
+ # Persistent compaction state for the current history generation.
109
+ self._summary_msg: dict | None = None # sent verbatim on every call
110
+ self._folded: list[dict] = [] # caller objects the summary replaces
111
+ self._last_seen: list[dict] = [] # previous call's untrimmed input
112
+ self._awaiting_measurement: bool = False # block until fresh prompt_tokens
113
+
88
114
  self._cache_monitor: CacheMonitor = CacheMonitor()
89
115
 
90
116
  # Event-correlation ids. The factory that builds this manager knows
@@ -211,21 +237,43 @@ class OllamaContextManager:
211
237
  history = list(messages)
212
238
 
213
239
  if not history:
240
+ self._reset_generation()
241
+ self._last_seen = []
214
242
  return [system_msg] if system_msg else []
215
243
 
216
- # Compact if threshold exceeded
217
- compaction_summary: dict | None = None
218
- if self.should_compact() and history:
219
- before_compact = len(history)
220
- history, compaction_summary = self._compact(history)
221
- self._emit_trim(before_compact, len(history), "compaction")
244
+ # A history that does not extend the previous call's input is a new
245
+ # generation: the old summary and the old measurement do not apply.
246
+ if not self._extends_last_seen(history):
247
+ self._reset_generation()
248
+ self._last_seen = list(history)
249
+
250
+ # Re-apply the current summary in place of the messages it folded.
251
+ surviving = history[len(self._folded):] if self._summary_msg else history
252
+ if not surviving:
253
+ self._reset_generation()
254
+ surviving = history
255
+
256
+ # Compact at most once per fresh measurement of this generation.
257
+ if not self._awaiting_measurement and self.should_compact():
258
+ surviving, newly_folded = self._compact(surviving)
259
+ if newly_folded:
260
+ previous = self._summary_msg["content"] if self._summary_msg else None
261
+ self._summary_msg = {
262
+ "role": "user",
263
+ "content": self._merge_summary(
264
+ previous, self._summary_lines(newly_folded)
265
+ ),
266
+ }
267
+ self._folded = self._folded + newly_folded
268
+ self._awaiting_measurement = True
269
+ self._emit_trim(len(history), len(surviving), "compaction")
222
270
 
223
271
  # Apply max_history cap — trim from the head on unit boundaries so
224
272
  # assistant+tool_calls stays atomic with its tool_result responses.
225
273
  # See opus47.md finding #3.
226
- if max_history > 0 and len(history) > max_history:
227
- before_trim = len(history)
228
- units = self._group_into_units(history)
274
+ if max_history > 0 and len(surviving) > max_history:
275
+ before_trim = len(surviving)
276
+ units = self._group_into_units(surviving)
229
277
  kept: list[list[dict]] = []
230
278
  msg_count = 0
231
279
  for unit in reversed(units):
@@ -233,16 +281,16 @@ class OllamaContextManager:
233
281
  break
234
282
  kept.insert(0, unit)
235
283
  msg_count += len(unit)
236
- history = [m for unit in kept for m in unit]
237
- self._emit_trim(before_trim, len(history), "max_history")
284
+ surviving = [m for unit in kept for m in unit]
285
+ self._emit_trim(before_trim, len(surviving), "max_history")
238
286
 
239
287
  # Build the output — stable prefix order
240
288
  result: list[dict] = []
241
289
  if system_msg:
242
290
  result.append(system_msg)
243
- if compaction_summary is not None:
244
- result.append(compaction_summary)
245
- result.extend(history)
291
+ if self._summary_msg is not None:
292
+ result.append(self._summary_msg)
293
+ result.extend(surviving)
246
294
  self._estimated_prompt_tokens = self._counter.count_messages(result)
247
295
  return result
248
296
 
@@ -256,6 +304,9 @@ class OllamaContextManager:
256
304
  self._real_completion_tokens = usage.get(
257
305
  "completion_tokens", self._real_completion_tokens
258
306
  )
307
+ if "prompt_tokens" in usage:
308
+ # A fresh measurement of the history that was just sent.
309
+ self._awaiting_measurement = False
259
310
  self._last_prompt_eval_ns = usage.get("prompt_eval_ns", self._last_prompt_eval_ns)
260
311
  self._last_eval_ns = usage.get("eval_ns", self._last_eval_ns)
261
312
 
@@ -398,8 +449,21 @@ class OllamaContextManager:
398
449
 
399
450
  return units
400
451
 
401
- def _heuristic_summarize(self, messages: list[dict]) -> str:
402
- """Produce a compact text summary of messages without calling an LLM."""
452
+ def _extends_last_seen(self, history: list[dict]) -> bool:
453
+ """True if the previous call's input is an identity prefix of ``history``."""
454
+ last = self._last_seen
455
+ if len(history) < len(last):
456
+ return False
457
+ return all(history[i] is last[i] for i in range(len(last)))
458
+
459
+ def _reset_generation(self) -> None:
460
+ """Forget the summary; wait for a measurement of the new history."""
461
+ self._summary_msg = None
462
+ self._folded = []
463
+ self._awaiting_measurement = True
464
+
465
+ def _summary_lines(self, messages: list[dict]) -> list[str]:
466
+ """One heuristic summary line per message, without calling an LLM."""
403
467
  parts: list[str] = []
404
468
  for msg in messages:
405
469
  role = msg["role"]
@@ -418,29 +482,57 @@ class OllamaContextManager:
418
482
  elif role == "tool":
419
483
  name = msg.get("name", "unknown")
420
484
  parts.append(f"Tool {name} returned result")
421
- return "[Previous conversation summary]\n" + "\n".join(parts)
485
+ return parts
486
+
487
+ def _heuristic_summarize(self, messages: list[dict]) -> str:
488
+ """Produce a compact text summary of messages without calling an LLM."""
489
+ return SUMMARY_HEADER + "\n" + "\n".join(self._summary_lines(messages))
490
+
491
+ def _merge_summary(self, previous: str | None, new_lines: list[str]) -> str:
492
+ """Fold ``new_lines`` into the previous summary under one header.
493
+
494
+ The body keeps the newest lines whose total length, newlines
495
+ included, is at most ``_SUMMARY_MAX_CHARS``. If the newest line alone
496
+ is longer, it is kept as its first ``_SUMMARY_MAX_CHARS - 1``
497
+ characters plus an ellipsis.
498
+ """
499
+ lines: list[str] = []
500
+ if previous:
501
+ body = previous.split("\n", 1)[1] if "\n" in previous else ""
502
+ lines = body.split("\n") if body else []
503
+ lines += new_lines
504
+
505
+ kept: list[str] = []
506
+ total = 0
507
+ for line in reversed(lines):
508
+ cost = len(line) + (1 if kept else 0)
509
+ if total + cost > _SUMMARY_MAX_CHARS:
510
+ break
511
+ kept.append(line)
512
+ total += cost
513
+ if not kept and lines:
514
+ kept = [lines[-1][: _SUMMARY_MAX_CHARS - 1] + "…"]
515
+ return SUMMARY_HEADER + "\n" + "\n".join(reversed(kept))
422
516
 
423
517
  def _compact(
424
518
  self, history: list[dict]
425
- ) -> tuple[list[dict], dict]:
426
- """Replace the oldest half of message units with a heuristic summary.
519
+ ) -> tuple[list[dict], list[dict]]:
520
+ """Fold the oldest half of message units, never the newest unit.
427
521
 
428
- Returns the surviving history and the summary message dict.
522
+ Returns ``(surviving, folded)``. With fewer than two units nothing is
523
+ folded: the newest unit is the request the model must see.
429
524
  """
430
525
  units = self._group_into_units(history)
526
+ if len(units) < 2:
527
+ return history, []
431
528
 
432
- # Compact the oldest half of units (at least 1)
433
- compact_count = max(1, len(units) // 2)
529
+ # Compact the oldest half of units (at least 1, never the newest)
530
+ compact_count = min(max(1, len(units) // 2), len(units) - 1)
434
531
  units_to_compact = units[:compact_count]
435
532
  units_to_keep = units[compact_count:]
436
533
 
437
- # Flatten compacted units for summarisation
438
- msgs_to_compact = [msg for unit in units_to_compact for msg in unit]
439
- compacted_msg_count = len(msgs_to_compact)
440
-
441
- summary_text = self._heuristic_summarize(msgs_to_compact)
442
- summary_msg = {"role": "user", "content": summary_text}
443
-
534
+ folded = [msg for unit in units_to_compact for msg in unit]
535
+ compacted_msg_count = len(folded)
444
536
  surviving = [msg for unit in units_to_keep for msg in unit]
445
537
 
446
538
  self._compaction_count += 1
@@ -453,4 +545,4 @@ class OllamaContextManager:
453
545
  self._compaction_count,
454
546
  )
455
547
 
456
- return surviving, summary_msg
548
+ return surviving, folded