tunacode-cli 0.0.31__tar.gz → 0.0.33__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.
Potentially problematic release.
This version of tunacode-cli might be problematic. Click here for more details.
- {tunacode_cli-0.0.31/src/tunacode_cli.egg-info → tunacode_cli-0.0.33}/PKG-INFO +1 -1
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/pyproject.toml +1 -1
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/commands.py +4 -4
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/repl.py +7 -3
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/defaults.py +1 -1
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/constants.py +1 -1
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/agents/main.py +7 -2
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33/src/tunacode_cli.egg-info}/PKG-INFO +1 -1
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/SOURCES.txt +4 -2
- tunacode_cli-0.0.33/src/tunacode_cli.egg-info/top_level.txt +1 -0
- tunacode_cli-0.0.33/tests/test_characterization_grep_performance.py +287 -0
- tunacode_cli-0.0.33/tests/test_characterization_iteration_limits.py +145 -0
- tunacode_cli-0.0.33/tests/test_characterization_tool_ui_behavior.py +464 -0
- tunacode_cli-0.0.33/tests/test_tool_handler_ui_messages.py +105 -0
- tunacode_cli-0.0.31/src/api/auth.py +0 -13
- tunacode_cli-0.0.31/src/api/users.py +0 -8
- tunacode_cli-0.0.31/src/tunacode_cli.egg-info/top_level.txt +0 -3
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/CLAUDE.md +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/LICENSE +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/MANIFEST.in +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/README.md +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/TUNACODE.md +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/setup.cfg +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/setup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/main.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/textual_app.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/cli/textual_bridge.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/models.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/configuration/settings.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/context.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/agents/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/background/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/background/manager.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/code_index.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/llm/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/agent_setup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/base.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/config_setup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/coordinator.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/environment_setup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/setup/git_safety_setup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/state.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/core/tool_handler.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/exceptions.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/prompts/system.md +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/py.typed +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/services/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/services/mcp.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/setup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/base.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/bash.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/glob.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/grep.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/list_dir.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/read_file.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/read_file_async_poc.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/run_command.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/update_file.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/tools/write_file.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/types.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/completers.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/console.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/constants.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/decorators.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/input.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/keybindings.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/lexers.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/output.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/panels.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/prompt_manager.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/tool_ui.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/ui/validators.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/bm25.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/diff_utils.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/file_utils.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/import_cache.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/ripgrep.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/system.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/text_utils.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/token_counter.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode/utils/user_configuration.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/src/tunacode_cli.egg-info/requires.txt +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/conftest.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_agent_creation.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_process_node.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_process_request.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/agent/test_tool_message_patching.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_background_edge_cases.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_cleanup.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_task_cancellation.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_task_creation.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/background/test_task_execution.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_cache_management.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_file_scanning.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_index_building.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_search_operations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/conftest.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_command_parsing.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_input_handling.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_multiline_input.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_repl_initialization.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/repl/test_session_flow.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_error_recovery.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_llm_routing.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_mcp_integration.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/services/test_service_lifecycle.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_agent_tracking.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_message_history.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_permissions.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_session_management.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_state_initialization.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/state/test_user_config.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/test_characterization_commands.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_async_ui.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_console_output.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_diff_display.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_prompt_rendering.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/ui/test_tool_confirmations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_file_operations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_git_commands.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_token_counting.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/conftest.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/crud/test_core_file_operations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/fixtures/__init__.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/fixtures/file_operations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_error_recovery_flow.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_full_session_flow.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_mcp_tool_flow.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_multi_tool_operations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/integration/test_performance_scenarios.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_actual_parallelism.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_agent_initialization.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_background_manager.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_agent_main.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_bash.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_commands_system.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_glob.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_grep.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_list_dir.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_read_file.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_repl_utils.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_run_command.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_setup_system.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_update_file.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_utilities.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_characterization_write_file.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_cli_command_flow.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_cli_file_operations_integration.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_config_setup_async.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_enhanced_visual_feedback.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_fallback_responses.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_fast_glob_search.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_operations_edge_cases.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_operations_stress.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_reference_context_tracking.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_file_reference_expansion.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_grep_fast_glob.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_grep_legacy_compat.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_grep_timeout.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_list_dir.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_execution_demo.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_execution_freeze_fix.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_execution_integration.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_read_only_tools.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_parallel_tool_execution.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_read_only_confirmation.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_tool_categorization.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_tool_combinations.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_update_command.py +0 -0
- {tunacode_cli-0.0.31 → tunacode_cli-0.0.33}/tests/test_visual_parallel_feedback.py +0 -0
|
@@ -184,8 +184,8 @@ class IterationsCommand(SimpleCommand):
|
|
|
184
184
|
if args:
|
|
185
185
|
try:
|
|
186
186
|
new_limit = int(args[0])
|
|
187
|
-
if new_limit < 1 or new_limit >
|
|
188
|
-
await ui.error("Iterations must be between 1 and
|
|
187
|
+
if new_limit < 1 or new_limit > 100:
|
|
188
|
+
await ui.error("Iterations must be between 1 and 100")
|
|
189
189
|
return
|
|
190
190
|
|
|
191
191
|
# Update the user config
|
|
@@ -198,9 +198,9 @@ class IterationsCommand(SimpleCommand):
|
|
|
198
198
|
except ValueError:
|
|
199
199
|
await ui.error("Please provide a valid number")
|
|
200
200
|
else:
|
|
201
|
-
current = state.user_config.get("settings", {}).get("max_iterations",
|
|
201
|
+
current = state.user_config.get("settings", {}).get("max_iterations", 40)
|
|
202
202
|
await ui.info(f"Current maximum iterations: {current}")
|
|
203
|
-
await ui.muted("Usage: /iterations <number> (1-
|
|
203
|
+
await ui.muted("Usage: /iterations <number> (1-100)")
|
|
204
204
|
|
|
205
205
|
|
|
206
206
|
class ClearCommand(SimpleCommand):
|
|
@@ -88,12 +88,16 @@ async def _tool_confirm(tool_call, node, state_manager: StateManager):
|
|
|
88
88
|
|
|
89
89
|
async def _tool_handler(part, node, state_manager: StateManager):
|
|
90
90
|
"""Handle tool execution with separated business logic and UI."""
|
|
91
|
-
|
|
91
|
+
# Create tool handler with state first to check if confirmation is needed
|
|
92
|
+
tool_handler = ToolHandler(state_manager)
|
|
93
|
+
|
|
94
|
+
# Only show tool info for tools that require confirmation
|
|
95
|
+
if tool_handler.should_confirm(part.tool_name):
|
|
96
|
+
await ui.info(f"Tool({part.tool_name})")
|
|
97
|
+
|
|
92
98
|
state_manager.session.spinner.stop()
|
|
93
99
|
|
|
94
100
|
try:
|
|
95
|
-
# Create tool handler with state
|
|
96
|
-
tool_handler = ToolHandler(state_manager)
|
|
97
101
|
args = _parse_args(part.args)
|
|
98
102
|
|
|
99
103
|
# Use a synchronous function in run_in_terminal to avoid async deadlocks
|
|
@@ -345,6 +345,11 @@ async def _process_node(
|
|
|
345
345
|
# Check if ALL tools in this node are read-only
|
|
346
346
|
all_read_only = all(part.tool_name in READ_ONLY_TOOLS for part in tool_parts)
|
|
347
347
|
|
|
348
|
+
# TODO: Currently only batches if ALL tools are read-only. Should be updated to use
|
|
349
|
+
# batch_read_only_tools() function to group consecutive read-only tools and execute
|
|
350
|
+
# them in parallel even when mixed with write/execute tools. For example:
|
|
351
|
+
# [read, read, write, read] should execute as: [read||read], [write], [read]
|
|
352
|
+
# instead of all sequential. The batch_read_only_tools() function exists but is unused.
|
|
348
353
|
if all_read_only and len(tool_parts) > 1 and buffering_callback:
|
|
349
354
|
# Execute read-only tools in parallel!
|
|
350
355
|
import time
|
|
@@ -651,8 +656,8 @@ async def process_request(
|
|
|
651
656
|
) -> AgentRun:
|
|
652
657
|
agent = get_or_create_agent(model, state_manager)
|
|
653
658
|
mh = state_manager.session.messages.copy()
|
|
654
|
-
# Get max iterations from config (default:
|
|
655
|
-
max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations",
|
|
659
|
+
# Get max iterations from config (default: 40)
|
|
660
|
+
max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 40)
|
|
656
661
|
fallback_enabled = state_manager.session.user_config.get("settings", {}).get(
|
|
657
662
|
"fallback_response", True
|
|
658
663
|
)
|
|
@@ -5,8 +5,6 @@ README.md
|
|
|
5
5
|
TUNACODE.md
|
|
6
6
|
pyproject.toml
|
|
7
7
|
setup.py
|
|
8
|
-
src/api/auth.py
|
|
9
|
-
src/api/users.py
|
|
10
8
|
src/tunacode/__init__.py
|
|
11
9
|
src/tunacode/constants.py
|
|
12
10
|
src/tunacode/context.py
|
|
@@ -92,11 +90,14 @@ tests/test_characterization_bash.py
|
|
|
92
90
|
tests/test_characterization_commands_system.py
|
|
93
91
|
tests/test_characterization_glob.py
|
|
94
92
|
tests/test_characterization_grep.py
|
|
93
|
+
tests/test_characterization_grep_performance.py
|
|
94
|
+
tests/test_characterization_iteration_limits.py
|
|
95
95
|
tests/test_characterization_list_dir.py
|
|
96
96
|
tests/test_characterization_read_file.py
|
|
97
97
|
tests/test_characterization_repl_utils.py
|
|
98
98
|
tests/test_characterization_run_command.py
|
|
99
99
|
tests/test_characterization_setup_system.py
|
|
100
|
+
tests/test_characterization_tool_ui_behavior.py
|
|
100
101
|
tests/test_characterization_update_file.py
|
|
101
102
|
tests/test_characterization_utilities.py
|
|
102
103
|
tests/test_characterization_write_file.py
|
|
@@ -123,6 +124,7 @@ tests/test_parallel_tool_execution.py
|
|
|
123
124
|
tests/test_read_only_confirmation.py
|
|
124
125
|
tests/test_tool_categorization.py
|
|
125
126
|
tests/test_tool_combinations.py
|
|
127
|
+
tests/test_tool_handler_ui_messages.py
|
|
126
128
|
tests/test_update_command.py
|
|
127
129
|
tests/test_visual_parallel_feedback.py
|
|
128
130
|
tests/characterization/conftest.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tunacode
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Characterization tests for grep performance behaviors.
|
|
3
|
+
These tests capture the CURRENT performance characteristics and strategy selection of the grep tool.
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import pytest
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from tunacode.tools.grep import grep
|
|
12
|
+
|
|
13
|
+
pytestmark = pytest.mark.asyncio
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestGrepPerformanceCharacterization:
|
|
17
|
+
"""Golden-master tests for grep performance behaviors and strategy selection."""
|
|
18
|
+
|
|
19
|
+
def setup_method(self):
|
|
20
|
+
"""Create a temporary directory with test files."""
|
|
21
|
+
self.temp_dir = tempfile.mkdtemp()
|
|
22
|
+
self.original_cwd = os.getcwd()
|
|
23
|
+
os.chdir(self.temp_dir)
|
|
24
|
+
|
|
25
|
+
def teardown_method(self):
|
|
26
|
+
"""Clean up temporary files."""
|
|
27
|
+
os.chdir(self.original_cwd)
|
|
28
|
+
import shutil
|
|
29
|
+
shutil.rmtree(self.temp_dir)
|
|
30
|
+
|
|
31
|
+
async def test_grep_fast_glob_prefilter_performance(self):
|
|
32
|
+
"""Capture performance behavior with fast-glob prefiltering."""
|
|
33
|
+
# Create many non-matching files
|
|
34
|
+
for i in range(100):
|
|
35
|
+
await self._create_file(f"data_{i}.txt", f"data file {i}")
|
|
36
|
+
await self._create_file(f"log_{i}.log", f"log entry {i}")
|
|
37
|
+
|
|
38
|
+
# Create a few matching Python files
|
|
39
|
+
for i in range(10):
|
|
40
|
+
content = f"""import os
|
|
41
|
+
def function_{i}():
|
|
42
|
+
# TODO: implement feature {i}
|
|
43
|
+
return {i}
|
|
44
|
+
"""
|
|
45
|
+
await self._create_file(f"module_{i}.py", content)
|
|
46
|
+
|
|
47
|
+
# Measure time for filtered search
|
|
48
|
+
start_time = time.time()
|
|
49
|
+
result = await grep("TODO", include_files="*.py", return_format="string")
|
|
50
|
+
filtered_time = time.time() - start_time
|
|
51
|
+
|
|
52
|
+
# Verify it found the matches
|
|
53
|
+
assert "Found" in result
|
|
54
|
+
assert "TODO" in result
|
|
55
|
+
lines = result.split("\n")
|
|
56
|
+
|
|
57
|
+
# Check for strategy information (if present)
|
|
58
|
+
strategy_mentioned = any("strategy" in line.lower() for line in lines)
|
|
59
|
+
|
|
60
|
+
# Filtered search should be relatively fast
|
|
61
|
+
assert filtered_time < 2.0 # Should complete within 2 seconds
|
|
62
|
+
assert isinstance(result, str)
|
|
63
|
+
|
|
64
|
+
async def test_grep_no_filter_performance(self):
|
|
65
|
+
"""Capture performance behavior without file filtering."""
|
|
66
|
+
# Create many files of different types
|
|
67
|
+
for i in range(50):
|
|
68
|
+
await self._create_file(f"file_{i}.txt", f"content {i}")
|
|
69
|
+
await self._create_file(f"script_{i}.py", f"# Python script {i}")
|
|
70
|
+
await self._create_file(f"config_{i}.json", f'{{"id": {i}}}')
|
|
71
|
+
|
|
72
|
+
# Search without filter
|
|
73
|
+
start_time = time.time()
|
|
74
|
+
result = await grep("content", return_format="string")
|
|
75
|
+
unfiltered_time = time.time() - start_time
|
|
76
|
+
|
|
77
|
+
# Should find matches in txt files
|
|
78
|
+
assert "Found" in result
|
|
79
|
+
assert unfiltered_time < 3.0 # Reasonable time limit
|
|
80
|
+
|
|
81
|
+
async def test_grep_complex_glob_patterns(self):
|
|
82
|
+
"""Capture behavior with complex glob patterns like *.{py,js}."""
|
|
83
|
+
# Create mixed file types
|
|
84
|
+
for i in range(20):
|
|
85
|
+
await self._create_file(f"module_{i}.py", f"# TODO: Python task {i}")
|
|
86
|
+
await self._create_file(f"script_{i}.js", f"// TODO: JavaScript task {i}")
|
|
87
|
+
await self._create_file(f"data_{i}.txt", f"TODO: Text task {i}")
|
|
88
|
+
|
|
89
|
+
# Search with complex glob
|
|
90
|
+
result = await grep("TODO", include_files="*.{py,js}", return_format="string")
|
|
91
|
+
|
|
92
|
+
# Should find matches in both .py and .js files
|
|
93
|
+
assert "Found" in result
|
|
94
|
+
lines = result.split("\n")
|
|
95
|
+
|
|
96
|
+
# Check if both file types are included
|
|
97
|
+
py_found = any(".py" in line for line in lines)
|
|
98
|
+
js_found = any(".js" in line for line in lines)
|
|
99
|
+
txt_found = any(".txt" in line for line in lines)
|
|
100
|
+
|
|
101
|
+
assert py_found or js_found # At least one should be found
|
|
102
|
+
assert not txt_found # .txt files should be excluded
|
|
103
|
+
|
|
104
|
+
async def test_grep_max_glob_limit(self):
|
|
105
|
+
"""Capture behavior when hitting MAX_GLOB limit."""
|
|
106
|
+
# Create more files than MAX_GLOB limit (assuming it's around 1000)
|
|
107
|
+
for i in range(200): # Create 200 Python files
|
|
108
|
+
await self._create_file(f"large_set_{i}.py", f"# File {i}\n# SEARCH_ME")
|
|
109
|
+
|
|
110
|
+
# Try to search with glob filter
|
|
111
|
+
result = await grep("SEARCH_ME", include_files="*.py", return_format="string")
|
|
112
|
+
|
|
113
|
+
# Should still work but might use different strategy
|
|
114
|
+
assert "Found" in result or "SEARCH_ME" in result
|
|
115
|
+
|
|
116
|
+
# Check if strategy information mentions limits
|
|
117
|
+
lines = result.split("\n")
|
|
118
|
+
strategy_line = next((line for line in lines if "strategy" in line.lower()), "")
|
|
119
|
+
|
|
120
|
+
# Capture actual behavior
|
|
121
|
+
assert isinstance(result, str)
|
|
122
|
+
|
|
123
|
+
async def test_grep_regex_with_prefilter(self):
|
|
124
|
+
"""Capture performance with regex patterns and file filtering."""
|
|
125
|
+
# Create test files
|
|
126
|
+
for i in range(30):
|
|
127
|
+
content = f"""import os
|
|
128
|
+
import sys
|
|
129
|
+
from pathlib import Path
|
|
130
|
+
|
|
131
|
+
def process_{i}():
|
|
132
|
+
return Path('/tmp/file_{i}')
|
|
133
|
+
"""
|
|
134
|
+
await self._create_file(f"processor_{i}.py", content)
|
|
135
|
+
await self._create_file(f"readme_{i}.md", f"# Documentation {i}\nUse Path objects")
|
|
136
|
+
|
|
137
|
+
# Regex search with filter
|
|
138
|
+
start_time = time.time()
|
|
139
|
+
result = await grep(r"import.*Path", use_regex=True, include_files="*.py", return_format="string")
|
|
140
|
+
regex_time = time.time() - start_time
|
|
141
|
+
|
|
142
|
+
# Should find matches efficiently
|
|
143
|
+
assert "Found" in result
|
|
144
|
+
assert regex_time < 2.0
|
|
145
|
+
|
|
146
|
+
# Should only match Python files
|
|
147
|
+
lines = result.split("\n")
|
|
148
|
+
assert not any(".md" in line for line in lines)
|
|
149
|
+
|
|
150
|
+
async def test_grep_deeply_nested_structure(self):
|
|
151
|
+
"""Capture behavior with deeply nested directory structures."""
|
|
152
|
+
# Create nested directories
|
|
153
|
+
nested_path = Path(".")
|
|
154
|
+
for i in range(5): # 5 levels deep
|
|
155
|
+
nested_path = nested_path / f"level_{i}"
|
|
156
|
+
nested_path.mkdir(exist_ok=True)
|
|
157
|
+
await self._create_file(str(nested_path / f"file_{i}.py"), f"# NESTED_PATTERN at level {i}")
|
|
158
|
+
|
|
159
|
+
# Search in nested structure
|
|
160
|
+
result = await grep("NESTED_PATTERN", return_format="list")
|
|
161
|
+
|
|
162
|
+
# Should find files at all levels
|
|
163
|
+
assert len(result) >= 5
|
|
164
|
+
|
|
165
|
+
# Check if deeply nested files are found
|
|
166
|
+
deepest_found = any("level_4" in f for f in result)
|
|
167
|
+
assert deepest_found
|
|
168
|
+
|
|
169
|
+
async def test_grep_mixed_encodings(self):
|
|
170
|
+
"""Capture behavior with files of different encodings."""
|
|
171
|
+
# Create files with different content types
|
|
172
|
+
await self._create_file("utf8.txt", "UTF-8: Hello 世界")
|
|
173
|
+
await self._create_file("ascii.txt", "ASCII: Hello World")
|
|
174
|
+
|
|
175
|
+
# Some files might have encoding issues
|
|
176
|
+
Path("latin1.txt").write_bytes("Latin-1: café".encode('latin-1'))
|
|
177
|
+
|
|
178
|
+
# Search for common pattern
|
|
179
|
+
result = await grep("Hello", return_format="list")
|
|
180
|
+
|
|
181
|
+
# Should handle UTF-8 and ASCII files
|
|
182
|
+
assert len(result) >= 2
|
|
183
|
+
assert any("utf8.txt" in f for f in result)
|
|
184
|
+
assert any("ascii.txt" in f for f in result)
|
|
185
|
+
|
|
186
|
+
async def test_grep_symlinks(self):
|
|
187
|
+
"""Capture behavior with symbolic links."""
|
|
188
|
+
# Create a real file
|
|
189
|
+
await self._create_file("original.py", "# SYMLINK_TEST pattern")
|
|
190
|
+
|
|
191
|
+
# Create a symlink (if supported by OS)
|
|
192
|
+
try:
|
|
193
|
+
Path("link_to_original.py").symlink_to("original.py")
|
|
194
|
+
has_symlink = True
|
|
195
|
+
except (OSError, NotImplementedError):
|
|
196
|
+
has_symlink = False
|
|
197
|
+
|
|
198
|
+
# Search for pattern
|
|
199
|
+
result = await grep("SYMLINK_TEST", return_format="list")
|
|
200
|
+
|
|
201
|
+
if has_symlink:
|
|
202
|
+
# Capture whether symlinks are followed or not
|
|
203
|
+
assert len(result) >= 1 # At least the original
|
|
204
|
+
# Record actual behavior regarding symlinks
|
|
205
|
+
else:
|
|
206
|
+
assert len(result) == 1 # Just the original
|
|
207
|
+
|
|
208
|
+
async def test_grep_performance_with_large_matches(self):
|
|
209
|
+
"""Capture behavior when many files match the pattern."""
|
|
210
|
+
# Create many files with the same pattern
|
|
211
|
+
for i in range(100):
|
|
212
|
+
await self._create_file(f"match_{i}.txt", f"Line 1\nCOMMON_PATTERN here\nLine 3")
|
|
213
|
+
|
|
214
|
+
# Time the search
|
|
215
|
+
start_time = time.time()
|
|
216
|
+
result = await grep("COMMON_PATTERN", return_format="string")
|
|
217
|
+
many_matches_time = time.time() - start_time
|
|
218
|
+
|
|
219
|
+
# Should complete in reasonable time even with many matches
|
|
220
|
+
assert many_matches_time < 5.0
|
|
221
|
+
assert "Found" in result
|
|
222
|
+
|
|
223
|
+
# Check if result mentions match count
|
|
224
|
+
lines = result.split("\n")
|
|
225
|
+
first_line = lines[0] if lines else ""
|
|
226
|
+
assert "100" in first_line or "matches" in first_line.lower()
|
|
227
|
+
|
|
228
|
+
async def test_grep_empty_directory(self):
|
|
229
|
+
"""Capture behavior in empty directories."""
|
|
230
|
+
# Create empty subdirectory
|
|
231
|
+
Path("empty_dir").mkdir()
|
|
232
|
+
|
|
233
|
+
# Search in empty directory
|
|
234
|
+
result = await grep("pattern", path="empty_dir", return_format="list")
|
|
235
|
+
|
|
236
|
+
# Should handle gracefully
|
|
237
|
+
assert isinstance(result, list)
|
|
238
|
+
assert len(result) == 0
|
|
239
|
+
|
|
240
|
+
async def test_grep_performance_first_match(self):
|
|
241
|
+
"""Capture first-match deadline behavior."""
|
|
242
|
+
# Create files where pattern appears later
|
|
243
|
+
for i in range(50):
|
|
244
|
+
# Pattern is not in first files
|
|
245
|
+
content = f"File {i} content\n" * 100 if i < 40 else f"File {i}\nTARGET_PATTERN\nMore content"
|
|
246
|
+
await self._create_file(f"delayed_{i}.txt", content)
|
|
247
|
+
|
|
248
|
+
# Search should respect deadline
|
|
249
|
+
start_time = time.time()
|
|
250
|
+
result = await grep("TARGET_PATTERN", return_format="string")
|
|
251
|
+
search_time = time.time() - start_time
|
|
252
|
+
|
|
253
|
+
# Should find matches (eventually)
|
|
254
|
+
assert "Found" in result or "TARGET_PATTERN" in result
|
|
255
|
+
|
|
256
|
+
# Search time might be affected by 3-second deadline
|
|
257
|
+
assert search_time < 10.0 # Should not take too long
|
|
258
|
+
|
|
259
|
+
async def test_grep_glob_prefilter_file_limit(self):
|
|
260
|
+
"""Capture behavior of MAX_GLOB limit affecting strategy."""
|
|
261
|
+
# Create exactly at the boundary of MAX_GLOB limit
|
|
262
|
+
# Assuming MAX_GLOB is 1000 based on the code
|
|
263
|
+
for i in range(1010): # Slightly over limit
|
|
264
|
+
await self._create_file(f"boundary_{i}.py", f"# Test file {i}")
|
|
265
|
+
|
|
266
|
+
# Add one file with our pattern
|
|
267
|
+
await self._create_file("target.py", "# BOUNDARY_TEST pattern")
|
|
268
|
+
|
|
269
|
+
# Search with glob that would match all Python files
|
|
270
|
+
result = await grep("BOUNDARY_TEST", include_files="*.py", return_format="string")
|
|
271
|
+
|
|
272
|
+
# Should still find the pattern
|
|
273
|
+
assert "Found" in result or "BOUNDARY_TEST" in result
|
|
274
|
+
|
|
275
|
+
# Check if strategy differs due to file count
|
|
276
|
+
lines = result.split("\n")
|
|
277
|
+
strategy_info = [line for line in lines if "strategy" in line.lower()]
|
|
278
|
+
|
|
279
|
+
# Capture actual strategy selection behavior
|
|
280
|
+
assert isinstance(result, str)
|
|
281
|
+
|
|
282
|
+
# Helper method
|
|
283
|
+
async def _create_file(self, filename: str, content: str) -> None:
|
|
284
|
+
"""Helper to create a file with content."""
|
|
285
|
+
path = Path(filename)
|
|
286
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
287
|
+
path.write_text(content)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Characterization tests for iteration limits.
|
|
3
|
+
|
|
4
|
+
These tests capture the CURRENT behavior of iteration limits in TunaCode.
|
|
5
|
+
This is a golden-master test that documents the existing behavior before
|
|
6
|
+
we make changes to update the defaults.
|
|
7
|
+
|
|
8
|
+
Current behavior captured:
|
|
9
|
+
- Default max_iterations: 40 (updated from 20)
|
|
10
|
+
- Command limit: 100 (updated from 50)
|
|
11
|
+
- Fallback on line 201 in commands.py shows 40 (was 15, now updated)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from unittest.mock import Mock, patch, AsyncMock
|
|
16
|
+
|
|
17
|
+
from tunacode.configuration.defaults import DEFAULT_USER_CONFIG
|
|
18
|
+
from tunacode.cli.commands import IterationsCommand
|
|
19
|
+
from tunacode.types import CommandContext
|
|
20
|
+
from tunacode.core.state import StateManager
|
|
21
|
+
|
|
22
|
+
class TestIterationLimitsCharacterization:
|
|
23
|
+
"""Golden-master tests for iteration limit behavior."""
|
|
24
|
+
|
|
25
|
+
def test_default_max_iterations_is_40(self):
|
|
26
|
+
"""Capture behavior: default max_iterations is set to 40 in DEFAULT_USER_CONFIG."""
|
|
27
|
+
assert DEFAULT_USER_CONFIG["settings"]["max_iterations"] == 40
|
|
28
|
+
|
|
29
|
+
@pytest.mark.asyncio
|
|
30
|
+
async def test_iterations_command_max_limit_is_100(self):
|
|
31
|
+
"""Capture behavior: /iterations command enforces maximum limit of 100."""
|
|
32
|
+
# Setup
|
|
33
|
+
cmd = IterationsCommand()
|
|
34
|
+
state_manager = Mock(spec=StateManager)
|
|
35
|
+
state_manager.session = Mock()
|
|
36
|
+
state_manager.session.user_config = {"settings": {}}
|
|
37
|
+
|
|
38
|
+
context = CommandContext(
|
|
39
|
+
state_manager=state_manager
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Mock UI functions
|
|
43
|
+
with patch("tunacode.cli.commands.ui.error") as mock_error:
|
|
44
|
+
# Test trying to set iterations to 101 (above limit)
|
|
45
|
+
await cmd.execute(["101"], context)
|
|
46
|
+
|
|
47
|
+
# Assert error was shown
|
|
48
|
+
mock_error.assert_called_once_with("Iterations must be between 1 and 100")
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_iterations_command_accepts_100(self):
|
|
52
|
+
"""Capture behavior: /iterations command accepts exactly 100 as valid."""
|
|
53
|
+
# Setup
|
|
54
|
+
cmd = IterationsCommand()
|
|
55
|
+
state_manager = Mock(spec=StateManager)
|
|
56
|
+
state_manager.session = Mock()
|
|
57
|
+
state_manager.session.user_config = {"settings": {}}
|
|
58
|
+
|
|
59
|
+
context = CommandContext(
|
|
60
|
+
state_manager=state_manager
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Mock UI functions
|
|
64
|
+
with patch("tunacode.cli.commands.ui.success") as mock_success:
|
|
65
|
+
with patch("tunacode.cli.commands.ui.muted") as mock_muted:
|
|
66
|
+
# Test setting iterations to 100 (at limit)
|
|
67
|
+
await cmd.execute(["100"], context)
|
|
68
|
+
|
|
69
|
+
# Assert success was shown
|
|
70
|
+
mock_success.assert_called_once_with("Maximum iterations set to 100")
|
|
71
|
+
mock_muted.assert_called_once_with("Higher values allow more complex reasoning but may be slower")
|
|
72
|
+
|
|
73
|
+
# Assert value was set
|
|
74
|
+
assert state_manager.session.user_config["settings"]["max_iterations"] == 100
|
|
75
|
+
|
|
76
|
+
@pytest.mark.asyncio
|
|
77
|
+
async def test_iterations_command_rejects_0_and_negative(self):
|
|
78
|
+
"""Capture behavior: /iterations command rejects 0 and negative values."""
|
|
79
|
+
# Setup
|
|
80
|
+
cmd = IterationsCommand()
|
|
81
|
+
state_manager = Mock(spec=StateManager)
|
|
82
|
+
state_manager.session = Mock()
|
|
83
|
+
state_manager.session.user_config = {"settings": {}}
|
|
84
|
+
|
|
85
|
+
context = CommandContext(
|
|
86
|
+
state_manager=state_manager
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Mock UI functions
|
|
90
|
+
with patch("tunacode.cli.commands.ui.error") as mock_error:
|
|
91
|
+
# Test 0
|
|
92
|
+
await cmd.execute(["0"], context)
|
|
93
|
+
mock_error.assert_called_with("Iterations must be between 1 and 100")
|
|
94
|
+
|
|
95
|
+
# Reset mock
|
|
96
|
+
mock_error.reset_mock()
|
|
97
|
+
|
|
98
|
+
# Test negative
|
|
99
|
+
await cmd.execute(["-5"], context)
|
|
100
|
+
mock_error.assert_called_with("Iterations must be between 1 and 100")
|
|
101
|
+
|
|
102
|
+
@pytest.mark.asyncio
|
|
103
|
+
async def test_iterations_command_display_shows_default(self):
|
|
104
|
+
"""Capture behavior: /iterations command shows default of 40 when no config exists."""
|
|
105
|
+
# Setup
|
|
106
|
+
cmd = IterationsCommand()
|
|
107
|
+
state_manager = Mock(spec=StateManager)
|
|
108
|
+
state_manager.session = Mock()
|
|
109
|
+
state_manager.session.user_config = {} # No settings
|
|
110
|
+
|
|
111
|
+
context = CommandContext(
|
|
112
|
+
state_manager=state_manager
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Mock UI functions
|
|
116
|
+
with patch("tunacode.cli.commands.ui.info") as mock_info:
|
|
117
|
+
with patch("tunacode.cli.commands.ui.muted") as mock_muted:
|
|
118
|
+
# Test showing current value without args
|
|
119
|
+
await cmd.execute([], context)
|
|
120
|
+
|
|
121
|
+
# Assert shows 40 as default (matching DEFAULT_USER_CONFIG)
|
|
122
|
+
mock_info.assert_called_once_with("Current maximum iterations: 40")
|
|
123
|
+
mock_muted.assert_called_once_with("Usage: /iterations <number> (1-100)")
|
|
124
|
+
|
|
125
|
+
def test_process_request_reads_max_iterations_from_config(self):
|
|
126
|
+
"""Document behavior: process_request reads max_iterations from config on line 660."""
|
|
127
|
+
# This is a documentation test to capture where max_iterations is read
|
|
128
|
+
# in the process_request function. The actual line is:
|
|
129
|
+
# max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 40)
|
|
130
|
+
|
|
131
|
+
# The process_request function:
|
|
132
|
+
# 1. Reads max_iterations from user config settings
|
|
133
|
+
# 2. Falls back to 40 if not specified
|
|
134
|
+
# 3. Uses this value to limit iteration count in the agent loop
|
|
135
|
+
# 4. Breaks the loop when i >= max_iterations
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def test_main_agent_reads_max_iterations_on_line_660(self):
|
|
139
|
+
"""Document that main.py reads max_iterations from config on line 660."""
|
|
140
|
+
# This is a documentation test to capture the specific line where
|
|
141
|
+
# max_iterations is read. The actual line is:
|
|
142
|
+
# max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 20)
|
|
143
|
+
|
|
144
|
+
# This confirms the fallback default is 20 when not specified in config
|
|
145
|
+
pass
|