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.
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/AGENTS.md +17 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/PKG-INFO +13 -4
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/README.md +12 -3
- ayder_cli-2.4.1/docs/GATE.md +28 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/PROJECT_STRUCTURE.md +6 -3
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/config.toml.example +10 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/pyproject.toml +1 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/check_exception_handlers.py +1 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/check_log_arguments.py +4 -4
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/execution_policy.py +24 -3
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/validation.py +6 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/config.py +11 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/config_migration.py +2 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/context.py +33 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/default_context_manager.py +33 -27
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/ollama_context_manager.py +122 -30
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/loops/chat_loop.py +438 -236
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/base.py +2 -1
- ayder_cli-2.4.1/src/ayder_cli/providers/evren.py +61 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/claude.py +26 -7
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/gemini.py +26 -22
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/openai.py +4 -9
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/themes/ayder.py +37 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/themes/claude.py +37 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/context.py +36 -5
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/note_tool.py +22 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/notes.py +33 -10
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/task_tool.py +29 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/tasks.py +59 -13
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/normalization.py +48 -29
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/app.py +2 -2
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/commands.py +41 -2
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/screens.py +38 -3
- ayder_cli-2.4.1/tests/application/test_argument_canonicalization.py +136 -0
- ayder_cli-2.4.1/tests/core/test_default_config_evren.py +44 -0
- ayder_cli-2.4.1/tests/core/test_default_context_newest_unit.py +133 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_event_emission.py +3 -2
- ayder_cli-2.4.1/tests/core/test_max_output_tokens_default.py +121 -0
- ayder_cli-2.4.1/tests/core/test_ollama_compaction_persistence.py +235 -0
- ayder_cli-2.4.1/tests/loops/test_tool_argument_integrity.py +247 -0
- ayder_cli-2.4.1/tests/loops/test_tool_batch_cancellation.py +506 -0
- ayder_cli-2.4.1/tests/providers/test_claude_stream_tool_calls.py +242 -0
- ayder_cli-2.4.1/tests/providers/test_evren.py +79 -0
- ayder_cli-2.4.1/tests/providers/test_gemini_function_calls.py +179 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_native.py +16 -0
- ayder_cli-2.4.1/tests/providers/test_openai_list_models.py +47 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_exception_gate.py +7 -4
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_log_argument_gate.py +6 -6
- ayder_cli-2.4.1/tests/tools/test_context_storage_confinement.py +150 -0
- ayder_cli-2.4.1/tests/tools/test_notes_confinement.py +129 -0
- ayder_cli-2.4.1/tests/tools/test_task_path_confinement.py +145 -0
- ayder_cli-2.4.1/tests/tui/test_evren_terms.py +230 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/uv.lock +1 -1
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.github/workflows/ci.yml +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.github/workflows/python-package.yml +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.gitignore +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/.python-version +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/LICENSE +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/README_AGENT_WORKFLOW.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/README_OLLAMA.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/cc.png +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/plans/2026-06-26-file-editor-improvements.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/plans/2026-06-26-session-save-resume.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/specs/2026-06-26-file-editor-improvements-design.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/docs/superpowers/specs/2026-06-26-session-save-resume-design.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/check_logging_imports.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/exception_baseline.txt +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/find_percent_logging.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/log_argument_baseline.txt +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/scripts/logging_gates.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/__main__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/run.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/runner.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/agents/worktree.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/README.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/message_contract.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/application/runtime_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/cli.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/cli_callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/cli_runner.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/console.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/cache_monitor.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/context_manager_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/reasoning.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/result.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/core/session.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/diagnostics.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/log.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/logging_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/loops/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/masking.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/parser.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/process_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/prompts.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/deepseek.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/glm.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/ollama_inspector.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/impl/qwen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/orchestrator.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/providers/retry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/services/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/services/interactions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/services/messaging_inbox.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/themes/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/background_process_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/filesystem.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/search.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/search_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/shell.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/skill.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/skill_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/task_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/utils_tools.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/web.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/definition.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/execution.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/hooks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/mcp.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/mcp_client.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_api.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_github.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/plugin_status.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/schemas.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tools/utils.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/adapter.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/helpers.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/keybindings.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/parser.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/rendering.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/theme_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/types.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/tui/widgets.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/ui.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/src/ayder_cli/version.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/COVERAGE.md +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_agent_consolidated.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_agent_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_agent_tools.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_integration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_queue.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_run.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_runner.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_runner_provider_unavailable.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_worktree_helpers.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/agents/test_worktree_isolation.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/application/test_execution_result_truthfulness.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/application/test_message_contract.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/application/test_runtime_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/client/test_main.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/conftest.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/test_execution_policy_parity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/test_runtime_wiring.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/convergence/test_validation_path.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_asyncio_bridge.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_cache_monitor.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_migration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_provider_logging.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_retry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_config_v2.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_context_manager_factory.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_default_context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_default_context_manager_should_compact.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_event_stream.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_exception_guarantee.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_exception_hooks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_log_facade.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_logging_config_keys.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_logging_sinks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_loguru_caplog.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_ollama_context_manager_max_history.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_parameter_aliasing.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_parser.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_reasoning_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_session.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_session_id_threading.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/core/test_sink_masking.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_base.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_chat_loop_hook.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_chat_loop_tool_truncation.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_loop_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/loops/test_required_args_aliases.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_base.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_errors.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_fallback.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_matrix.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_minimax.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/ollama_drivers/test_registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_glm_async.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_inspector.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_parallel_tool_call_ids.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml_autoroute.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_ollama_xml_usage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_orchestrator_capabilities.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_provider_channel_binding.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_provider_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_qwen_async.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_reasoning_effort.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_retry_classifier.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_retry_integration.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/providers/test_retrying_provider.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_logging_policy_gates.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/scripts/test_loguru_private_shape.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/services/test_boundary.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/services/test_interaction_sink.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/services/test_messaging_inbox.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_cli.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_cli_callbacks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_context.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_env_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_logging_config.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_notes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_process_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/test_tasks.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_agent_notes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_background_process_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_bash_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_definition_discovery.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_di_injection.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_file_editor_hardening.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_file_editor_size_limit.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_impl.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_impl_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_mcp.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_mcp_client.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_note_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_notes_api.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_path_security.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_api.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_github.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_loading.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_manager.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_plugin_status.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_registry.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_registry_coverage.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_result.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_schemas.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_search_codebase.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_search_hardening.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_skill_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_task_tool.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_tool_severity.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_unexpected_args.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_utils.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tools/test_web.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/__init__.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_agent_list_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_do_clear_coordination.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_follow_mode.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_help_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_input_box.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_keybindings.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_multi_select_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_multiline_input.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_paste.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_peer_message_turn.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_pending_compact_consumer.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_pull_delivery.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_resume.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_status_bar_badges.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_themes.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_turn_consumer.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/tui/test_widgets.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_confirm_screen.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_diff_preview.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_plugin_autoenable.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_provider_switch.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_run_tui_startup.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_assistant_sanitization.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_chat_loop.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_agent.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_effort.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_logging.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_plugin.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_provider.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_commands_skill.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_helpers.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_temporal_command.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_tui_widgets.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_ui.py +0 -0
- {ayder_cli-2.3.1 → ayder_cli-2.4.1}/tests/ui/test_ui_coverage.py +0 -0
- {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
|
+
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
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
|
# =============================================================================
|
|
@@ -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 =
|
|
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
|
|
207
|
-
# package-wide shape/count rule rather than
|
|
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 =
|
|
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":
|
|
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 →
|
|
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=
|
|
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,
|
|
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=
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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(
|