tunacode-cli 0.0.53__tar.gz → 0.0.55__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.55/CLAUDE.md +78 -0
- {tunacode_cli-0.0.53/src/tunacode_cli.egg-info → tunacode_cli-0.0.55}/PKG-INFO +1 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/pyproject.toml +18 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/__init__.py +2 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/registry.py +4 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl.py +23 -7
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/tool_executor.py +6 -4
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/constants.py +1 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/__init__.py +20 -0
- tunacode_cli-0.0.55/src/tunacode/core/agents/agent_components/agent_helpers.py +219 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/node_processor.py +39 -74
- tunacode_cli-0.0.55/src/tunacode/core/agents/agent_components/truncation_checker.py +81 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/main.py +86 -312
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/state.py +7 -3
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/prompts/system.md +5 -4
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep.py +12 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/console.py +2 -0
- tunacode_cli-0.0.55/src/tunacode/ui/keybindings.py +50 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/output.py +39 -4
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/panels.py +56 -2
- tunacode_cli-0.0.55/src/tunacode/ui/tool_descriptions.py +115 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55/src/tunacode_cli.egg-info}/PKG-INFO +1 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/SOURCES.txt +6 -0
- tunacode_cli-0.0.55/tests/characterization/repl/test_escape_key_behavior.py +98 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_keyboard_interrupts.py +1 -1
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_repl.py +2 -2
- tunacode_cli-0.0.55/tests/characterization/ui/test_spinner_messages.py +99 -0
- tunacode_cli-0.0.55/tests/test_spinner_updates.py +110 -0
- tunacode_cli-0.0.53/CLAUDE.md +0 -176
- tunacode_cli-0.0.53/src/tunacode/ui/keybindings.py +0 -82
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/LICENSE +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/MANIFEST.in +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/README.md +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/setup.cfg +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/base.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/conversation.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/debug.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/development.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/model.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/system.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/template.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/todo.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/template_shortcut.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/main.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/command_parser.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/error_recovery.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/output_display.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/defaults.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/models.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/settings.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/context.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/agent_config.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/json_tool_parser.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/message_handler.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/response_state.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/result_wrapper.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/task_completion.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/tool_buffer.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/tool_executor.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/utils.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/background/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/background/manager.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/code_index.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/llm/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/config.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/formatters.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/handlers.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/logger.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/agent_setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/base.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/config_setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/coordinator.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/environment_setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/git_safety_setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/template_setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/token_usage/api_response_parser.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/token_usage/cost_calculator.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/token_usage/usage_tracker.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/tool_handler.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/exceptions.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/py.typed +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/services/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/services/mcp.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/setup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/templates/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/templates/loader.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/base.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/bash.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/glob.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/file_filter.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/pattern_matcher.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/result_formatter.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/search_result.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/list_dir.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/read_file.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/read_file_async_poc.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/run_command.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/todo.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/update_file.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/write_file.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/types.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/completers.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/constants.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/decorators.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/input.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/lexers.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/logging_compat.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/prompt_manager.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/tool_ui.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/utils.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/validators.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/bm25.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/diff_utils.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/file_utils.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/import_cache.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/message_utils.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/retry.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/ripgrep.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/security.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/system.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/text_utils.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/token_counter.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/user_configuration.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/requires.txt +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/top_level.txt +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/conftest.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_agent_creation.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_process_node.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_process_request.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_tool_message_patching.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_background_edge_cases.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_cleanup.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_task_cancellation.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_task_creation.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_task_execution.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_cache_management.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_file_scanning.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_index_building.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_search_operations.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/commands/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/commands/test_init_command.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/conftest.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/__init__.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_context_acceptance.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_context_integration.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_context_loading.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_tunacode_logging.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_command_parsing.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_error_handling.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_input_handling.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_multiline_input.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_output_display_logic.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_repl_initialization.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_session_flow.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_error_recovery.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_llm_routing.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_mcp_integration.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_service_lifecycle.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_agent_tracking.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_message_history.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_permissions.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_session_management.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_state_initialization.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_user_config.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_commands.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_grep.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_main.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_async_ui.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_console_output.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_diff_display.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_prompt_rendering.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_tool_confirmations.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_expand_file_refs.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_file_operations.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_git_commands.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_token_counting.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/conftest.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_agent_output_formatting.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_completion_detection.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_json_retry.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_logging_config.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_phase2_type_hints.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_prompt_changes_validation.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_security.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_tool_batching_retry.py +0 -0
- {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/unit/test_constants_enums.py +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
### Documentation
|
|
2
|
+
|
|
3
|
+
- update the documents @documentation and in .claude after any update.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
- use the subagent tech-docs-maintainer to update the documentation you MUST instruct the subagent to keep doc updates short you will be PUNISHED for not telling the documentation agent to keep it to only the most distilled information
|
|
7
|
+
|
|
8
|
+
- always follow best practices with git commits naming and gh cli workflows
|
|
9
|
+
|
|
10
|
+
- commit frequently
|
|
11
|
+
|
|
12
|
+
- always be on the side of safety, if you have any question consult the user
|
|
13
|
+
|
|
14
|
+
### Python Coding Standards
|
|
15
|
+
|
|
16
|
+
- Use type hints (PEP 484) for all function signatures
|
|
17
|
+
- Prefer f-strings (PEP 498) over %-formatting or .format()
|
|
18
|
+
- Use pathlib.Path instead of os.path for filesystem operations
|
|
19
|
+
- Structure imports: stdlib → third-party → local (PEP 8)
|
|
20
|
+
- Use dataclasses (PEP 557) for simple data containers
|
|
21
|
+
- Prefer context managers (with) for resource handling
|
|
22
|
+
- Use structural pattern matching (PEP 634) for complex
|
|
23
|
+
- run ruff frequently
|
|
24
|
+
|
|
25
|
+
### Testing
|
|
26
|
+
|
|
27
|
+
- "make test" command the entire testing suite
|
|
28
|
+
|
|
29
|
+
- anytime a new feature or refactor is done, we MUST find or make the golden/character test FIRST as a baseline standaard BEFORE starting, under no circumstance are you to not follow this pattern
|
|
30
|
+
|
|
31
|
+
### Workflow
|
|
32
|
+
|
|
33
|
+
- before any updates make a git commit rollback point, clearly labeled for future agents
|
|
34
|
+
|
|
35
|
+
- the clear outline of the objective MUST be established before we begin ANY coding, do not under any circumstance begin any updates untill this is clearly understood, if you have any ambiuguity or quesiton, the user can be brought in or use best practises
|
|
36
|
+
|
|
37
|
+
- use scratchpad-multi.sh as you work, after the MD file is done being used sort it to the approate directory
|
|
38
|
+
|
|
39
|
+
- the MD file created by the bash file MUST be used for the duiration of the task you will be PUNISHED if you do not update this file as you work.
|
|
40
|
+
|
|
41
|
+
- any key logic or file's must be inlcuded here in the following format if this format is not followed you will STOP reasses, and begin again
|
|
42
|
+
|
|
43
|
+
### Scratchpad Template Example
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
# Implementing Authentication Module
|
|
47
|
+
_Started: 2025-08-06 10:00:00_
|
|
48
|
+
_Agent: default_
|
|
49
|
+
|
|
50
|
+
[1] Found auth logic in src/auth/handler.py:45
|
|
51
|
+
[2] Key dependencies: jwt, bcrypt, session_manager
|
|
52
|
+
[3] Modified login function to add rate limiting
|
|
53
|
+
[3~1] Fixed edge case for empty passwords
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- General documentation → archive to @documentation/
|
|
57
|
+
- Developer/tunacode-specific → archive to @ .claude
|
|
58
|
+
- Organize archives by category (agent/development/ etc)
|
|
59
|
+
- In general the scratchpad should never go in any other dirs ececpt the two above
|
|
60
|
+
|
|
61
|
+
- if a task at hand is to big to handle as a one off use the taskmaster MCP but in general this should only be used as needed, usually it will not be needed
|
|
62
|
+
|
|
63
|
+
- grep documentation and .claude as needed BOTH of these have a README.md that ahs a direcoty map, you MUST read these before any bigger grep or context searches
|
|
64
|
+
|
|
65
|
+
- in general gather as much context as needed, unless specified by the user
|
|
66
|
+
|
|
67
|
+
- this is the most important part of this prompt: Synthesis context aggressively and heuristically AS NEEDED ONLY You can deploy the appropriate subagent for complex tasks agents list below
|
|
68
|
+
|
|
69
|
+
## Available Agents:
|
|
70
|
+
|
|
71
|
+
1. **bug-context-analyzer** - Investigates precise context around bugs without suggesting fixes
|
|
72
|
+
2. **code-synthesis-analyzer** - Analyzes recent code changes to identify issues needing fixes
|
|
73
|
+
3. **documentation-synthesis-qa** - Creates comprehensive docs via multi-agent orchestration
|
|
74
|
+
4. **expert-debugger** - Debugs issues using strategic logging and root cause analysis
|
|
75
|
+
5. **phased-task-processor** - Breaks down markdown tasks into max 5 actionable phases
|
|
76
|
+
6. **prompt-engineer** - Optimizes prompts using 26 documented engineering principles
|
|
77
|
+
7. **rapid-code-synthesis-qa** - Quick quality assessment with confidence scores (1-5 scale)
|
|
78
|
+
8. **tech-docs-maintainer** - Updates docs in @documentation and .claude directories
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tunacode-cli"
|
|
7
7
|
|
|
8
|
-
version = "0.0.
|
|
8
|
+
version = "0.0.55"
|
|
9
9
|
description = "Your agentic CLI developer."
|
|
10
10
|
keywords = ["cli", "agent", "development", "automation"]
|
|
11
11
|
readme = "README.md"
|
|
@@ -129,3 +129,20 @@ skips = ["B404", "B603", "B101", "B607", "B110", "B324", "B103", "B604", "B602",
|
|
|
129
129
|
# B604: shell=True (validated usage in REPL)
|
|
130
130
|
# B602: subprocess with shell=True (security utils)
|
|
131
131
|
# B108: hardcoded tmp paths (test fixtures)
|
|
132
|
+
|
|
133
|
+
[tool.mypy]
|
|
134
|
+
python_version = "3.10"
|
|
135
|
+
ignore_missing_imports = true
|
|
136
|
+
warn_return_any = false
|
|
137
|
+
warn_unused_configs = true
|
|
138
|
+
disallow_untyped_defs = false
|
|
139
|
+
disallow_incomplete_defs = false
|
|
140
|
+
check_untyped_defs = false
|
|
141
|
+
disallow_untyped_decorators = false
|
|
142
|
+
no_implicit_optional = true
|
|
143
|
+
warn_redundant_casts = true
|
|
144
|
+
warn_unused_ignores = false
|
|
145
|
+
warn_no_return = true
|
|
146
|
+
follow_imports = "silent"
|
|
147
|
+
# Allow CLAUDE_ANCHOR comments in docstrings
|
|
148
|
+
disable_error_code = ["annotation-unchecked"]
|
|
@@ -7,6 +7,8 @@ This package provides a modular command system with:
|
|
|
7
7
|
|
|
8
8
|
The main public API provides backward compatibility with the original
|
|
9
9
|
commands.py module while enabling better organization and maintainability.
|
|
10
|
+
|
|
11
|
+
CLAUDE_ANCHOR[commands-module]: Command registry and dispatch system
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
# Import base classes and infrastructure
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
"""Command registry and factory for TunaCode CLI commands.
|
|
1
|
+
"""Command registry and factory for TunaCode CLI commands.
|
|
2
|
+
|
|
3
|
+
CLAUDE_ANCHOR[command-registry]: Central command registration and execution
|
|
4
|
+
"""
|
|
2
5
|
|
|
3
6
|
from dataclasses import dataclass
|
|
4
7
|
from typing import Any, Dict, List, Optional, Type
|
|
@@ -3,6 +3,8 @@ Module: tunacode.cli.repl
|
|
|
3
3
|
|
|
4
4
|
Interactive REPL (Read-Eval-Print Loop) implementation for TunaCode.
|
|
5
5
|
Handles user input, command processing, and agent interaction in an interactive shell.
|
|
6
|
+
|
|
7
|
+
CLAUDE_ANCHOR[repl-module]: Core REPL loop and user interaction handling
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
# ============================================================================
|
|
@@ -41,7 +43,7 @@ MSG_TOOL_INTERRUPTED = "Tool execution was interrupted"
|
|
|
41
43
|
MSG_REQUEST_CANCELLED = "Request cancelled"
|
|
42
44
|
MSG_SESSION_ENDED = "Session ended. Happy coding!"
|
|
43
45
|
MSG_AGENT_BUSY = "Agent is busy, press Ctrl+C to interrupt."
|
|
44
|
-
|
|
46
|
+
MSG_HIT_ABORT_KEY = "Hit ESC or Ctrl+C again to exit"
|
|
45
47
|
SHELL_ENV_VAR = "SHELL"
|
|
46
48
|
DEFAULT_SHELL = "bash"
|
|
47
49
|
|
|
@@ -94,7 +96,10 @@ async def _handle_command(command: str, state_manager: StateManager) -> CommandR
|
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
async def process_request(text: str, state_manager: StateManager, output: bool = True):
|
|
97
|
-
"""Process input using the agent, handling cancellation safely.
|
|
99
|
+
"""Process input using the agent, handling cancellation safely.
|
|
100
|
+
|
|
101
|
+
CLAUDE_ANCHOR[process-request-repl]: REPL's main request processor with error handling
|
|
102
|
+
"""
|
|
98
103
|
import uuid
|
|
99
104
|
|
|
100
105
|
# Generate a unique ID for this request for correlated logging
|
|
@@ -245,7 +250,8 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
245
250
|
async def repl(state_manager: StateManager):
|
|
246
251
|
"""Main REPL loop that handles user interaction and input processing."""
|
|
247
252
|
action = None
|
|
248
|
-
|
|
253
|
+
abort_pressed = False
|
|
254
|
+
last_abort_time = 0.0
|
|
249
255
|
|
|
250
256
|
model_name = state_manager.session.current_model
|
|
251
257
|
max_tokens = (
|
|
@@ -270,16 +276,26 @@ async def repl(state_manager: StateManager):
|
|
|
270
276
|
try:
|
|
271
277
|
line = await ui.multiline_input(state_manager, _command_registry)
|
|
272
278
|
except UserAbortError:
|
|
273
|
-
|
|
279
|
+
import time
|
|
280
|
+
|
|
281
|
+
current_time = time.time()
|
|
282
|
+
|
|
283
|
+
# Reset if more than 3 seconds have passed
|
|
284
|
+
if current_time - last_abort_time > 3.0:
|
|
285
|
+
abort_pressed = False
|
|
286
|
+
|
|
287
|
+
if abort_pressed:
|
|
274
288
|
break
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
|
|
290
|
+
abort_pressed = True
|
|
291
|
+
last_abort_time = current_time
|
|
292
|
+
await ui.warning(MSG_HIT_ABORT_KEY)
|
|
277
293
|
continue
|
|
278
294
|
|
|
279
295
|
if not line:
|
|
280
296
|
continue
|
|
281
297
|
|
|
282
|
-
|
|
298
|
+
abort_pressed = False
|
|
283
299
|
|
|
284
300
|
if line.lower() in ["exit", "quit"]:
|
|
285
301
|
break
|
{tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/tool_executor.py
RENAMED
|
@@ -43,8 +43,9 @@ async def tool_handler(part, state_manager: StateManager):
|
|
|
43
43
|
if tool_handler_instance.should_confirm(part.tool_name):
|
|
44
44
|
await ui.info(f"Tool({part.tool_name})")
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
# Keep spinner running during tool execution - it will be updated with tool status
|
|
47
|
+
# if not state_manager.session.is_streaming_active and state_manager.session.spinner:
|
|
48
|
+
# state_manager.session.spinner.stop()
|
|
48
49
|
|
|
49
50
|
streaming_panel = None
|
|
50
51
|
if state_manager.session.is_streaming_active and hasattr(
|
|
@@ -80,5 +81,6 @@ async def tool_handler(part, state_manager: StateManager):
|
|
|
80
81
|
if streaming_panel and tool_handler_instance.should_confirm(part.tool_name):
|
|
81
82
|
await streaming_panel.start()
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
# Spinner continues running - no need to restart
|
|
85
|
+
# if not state_manager.session.is_streaming_active and state_manager.session.spinner:
|
|
86
|
+
# state_manager.session.spinner.start()
|
{tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/__init__.py
RENAMED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"""Agent components package for modular agent functionality."""
|
|
2
2
|
|
|
3
3
|
from .agent_config import get_or_create_agent
|
|
4
|
+
from .agent_helpers import (
|
|
5
|
+
create_empty_response_message,
|
|
6
|
+
create_fallback_response,
|
|
7
|
+
create_progress_summary,
|
|
8
|
+
create_user_message,
|
|
9
|
+
format_fallback_output,
|
|
10
|
+
get_recent_tools_context,
|
|
11
|
+
get_tool_description,
|
|
12
|
+
get_tool_summary,
|
|
13
|
+
get_user_prompt_part_class,
|
|
14
|
+
)
|
|
4
15
|
from .json_tool_parser import extract_and_execute_tool_calls, parse_json_tool_calls
|
|
5
16
|
from .message_handler import get_model_messages, patch_tool_messages
|
|
6
17
|
from .node_processor import _process_node
|
|
@@ -24,4 +35,13 @@ __all__ = [
|
|
|
24
35
|
"check_task_completion",
|
|
25
36
|
"ToolBuffer",
|
|
26
37
|
"execute_tools_parallel",
|
|
38
|
+
"create_empty_response_message",
|
|
39
|
+
"create_fallback_response",
|
|
40
|
+
"create_progress_summary",
|
|
41
|
+
"create_user_message",
|
|
42
|
+
"format_fallback_output",
|
|
43
|
+
"get_recent_tools_context",
|
|
44
|
+
"get_tool_description",
|
|
45
|
+
"get_tool_summary",
|
|
46
|
+
"get_user_prompt_part_class",
|
|
27
47
|
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Helper functions for agent operations to reduce code duplication."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from tunacode.core.state import StateManager
|
|
6
|
+
from tunacode.types import FallbackResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UserPromptPartFallback:
|
|
10
|
+
"""Fallback class for UserPromptPart when pydantic_ai is not available."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, content: str, part_kind: str):
|
|
13
|
+
self.content = content
|
|
14
|
+
self.part_kind = part_kind
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Cache for UserPromptPart class
|
|
18
|
+
_USER_PROMPT_PART_CLASS = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_user_prompt_part_class():
|
|
22
|
+
"""Get UserPromptPart class with caching and fallback for test environment."""
|
|
23
|
+
global _USER_PROMPT_PART_CLASS
|
|
24
|
+
|
|
25
|
+
if _USER_PROMPT_PART_CLASS is not None:
|
|
26
|
+
return _USER_PROMPT_PART_CLASS
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import importlib
|
|
30
|
+
|
|
31
|
+
messages = importlib.import_module("pydantic_ai.messages")
|
|
32
|
+
_USER_PROMPT_PART_CLASS = getattr(messages, "UserPromptPart", None)
|
|
33
|
+
|
|
34
|
+
if _USER_PROMPT_PART_CLASS is None:
|
|
35
|
+
_USER_PROMPT_PART_CLASS = UserPromptPartFallback
|
|
36
|
+
except Exception:
|
|
37
|
+
_USER_PROMPT_PART_CLASS = UserPromptPartFallback
|
|
38
|
+
|
|
39
|
+
return _USER_PROMPT_PART_CLASS
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_user_message(content: str, state_manager: StateManager):
|
|
43
|
+
"""Create a user message and add it to the session messages."""
|
|
44
|
+
from .message_handler import get_model_messages
|
|
45
|
+
|
|
46
|
+
model_request_cls = get_model_messages()[0]
|
|
47
|
+
UserPromptPart = get_user_prompt_part_class()
|
|
48
|
+
user_prompt_part = UserPromptPart(content=content, part_kind="user-prompt")
|
|
49
|
+
message = model_request_cls(parts=[user_prompt_part], kind="request")
|
|
50
|
+
state_manager.session.messages.append(message)
|
|
51
|
+
return message
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_tool_summary(tool_calls: list[dict[str, Any]]) -> dict[str, int]:
|
|
55
|
+
"""Generate a summary of tool usage from tool calls."""
|
|
56
|
+
tool_summary: dict[str, int] = {}
|
|
57
|
+
for tc in tool_calls:
|
|
58
|
+
tool_name = tc.get("tool", "unknown")
|
|
59
|
+
tool_summary[tool_name] = tool_summary.get(tool_name, 0) + 1
|
|
60
|
+
return tool_summary
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_tool_description(tool_name: str, tool_args: dict[str, Any]) -> str:
|
|
64
|
+
"""Get a descriptive string for a tool call."""
|
|
65
|
+
tool_desc = tool_name
|
|
66
|
+
if tool_name in ["grep", "glob"] and isinstance(tool_args, dict):
|
|
67
|
+
pattern = tool_args.get("pattern", "")
|
|
68
|
+
tool_desc = f"{tool_name}('{pattern}')"
|
|
69
|
+
elif tool_name == "read_file" and isinstance(tool_args, dict):
|
|
70
|
+
path = tool_args.get("file_path", tool_args.get("filepath", ""))
|
|
71
|
+
tool_desc = f"{tool_name}('{path}')"
|
|
72
|
+
return tool_desc
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_recent_tools_context(tool_calls: list[dict[str, Any]], limit: int = 3) -> str:
|
|
76
|
+
"""Get a context string describing recent tool usage."""
|
|
77
|
+
if not tool_calls:
|
|
78
|
+
return "No tools used yet"
|
|
79
|
+
|
|
80
|
+
last_tools = []
|
|
81
|
+
for tc in tool_calls[-limit:]:
|
|
82
|
+
tool_name = tc.get("tool", "unknown")
|
|
83
|
+
tool_args = tc.get("args", {})
|
|
84
|
+
tool_desc = get_tool_description(tool_name, tool_args)
|
|
85
|
+
last_tools.append(tool_desc)
|
|
86
|
+
|
|
87
|
+
return f"Recent tools: {', '.join(last_tools)}"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_empty_response_message(
|
|
91
|
+
message: str,
|
|
92
|
+
empty_reason: str,
|
|
93
|
+
tool_calls: list[dict[str, Any]],
|
|
94
|
+
iteration: int,
|
|
95
|
+
state_manager: StateManager,
|
|
96
|
+
) -> str:
|
|
97
|
+
"""Create an aggressive message for handling empty responses."""
|
|
98
|
+
tools_context = get_recent_tools_context(tool_calls)
|
|
99
|
+
|
|
100
|
+
content = f"""FAILURE DETECTED: You returned {("an " + empty_reason if empty_reason != "empty" else "an empty")} response.
|
|
101
|
+
|
|
102
|
+
This is UNACCEPTABLE. You FAILED to produce output.
|
|
103
|
+
|
|
104
|
+
Task: {message[:200]}...
|
|
105
|
+
{tools_context}
|
|
106
|
+
Current iteration: {iteration}
|
|
107
|
+
|
|
108
|
+
TRY AGAIN RIGHT NOW:
|
|
109
|
+
|
|
110
|
+
1. If your search returned no results → Try a DIFFERENT search pattern
|
|
111
|
+
2. If you found what you need → Use TUNACODE_TASK_COMPLETE
|
|
112
|
+
3. If you're stuck → EXPLAIN SPECIFICALLY what's blocking you
|
|
113
|
+
4. If you need to explore → Use list_dir or broader searches
|
|
114
|
+
|
|
115
|
+
YOU MUST PRODUCE REAL OUTPUT IN THIS RESPONSE. NO EXCUSES.
|
|
116
|
+
EXECUTE A TOOL OR PROVIDE SUBSTANTIAL CONTENT.
|
|
117
|
+
DO NOT RETURN ANOTHER EMPTY RESPONSE."""
|
|
118
|
+
|
|
119
|
+
return content
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def create_progress_summary(tool_calls: list[dict[str, Any]]) -> tuple[dict[str, int], str]:
|
|
123
|
+
"""Create a progress summary from tool calls."""
|
|
124
|
+
tool_summary = get_tool_summary(tool_calls)
|
|
125
|
+
|
|
126
|
+
if tool_summary:
|
|
127
|
+
summary_str = ", ".join([f"{name}: {count}" for name, count in tool_summary.items()])
|
|
128
|
+
else:
|
|
129
|
+
summary_str = "No tools used yet"
|
|
130
|
+
|
|
131
|
+
return tool_summary, summary_str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def create_fallback_response(
|
|
135
|
+
iterations: int,
|
|
136
|
+
max_iterations: int,
|
|
137
|
+
tool_calls: list[dict[str, Any]],
|
|
138
|
+
messages: list[Any],
|
|
139
|
+
verbosity: str = "normal",
|
|
140
|
+
) -> FallbackResponse:
|
|
141
|
+
"""Create a comprehensive fallback response when iteration limit is reached."""
|
|
142
|
+
fallback = FallbackResponse(
|
|
143
|
+
summary="Reached maximum iterations without producing a final response.",
|
|
144
|
+
progress=f"Completed {iterations} iterations (limit: {max_iterations})",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Extract context from messages
|
|
148
|
+
tool_calls_summary = []
|
|
149
|
+
files_modified = set()
|
|
150
|
+
commands_run = []
|
|
151
|
+
|
|
152
|
+
for msg in messages:
|
|
153
|
+
if hasattr(msg, "parts"):
|
|
154
|
+
for part in msg.parts:
|
|
155
|
+
if hasattr(part, "part_kind") and part.part_kind == "tool-call":
|
|
156
|
+
tool_name = getattr(part, "tool_name", "unknown")
|
|
157
|
+
tool_calls_summary.append(tool_name)
|
|
158
|
+
|
|
159
|
+
# Track specific operations
|
|
160
|
+
if tool_name in ["write_file", "update_file"] and hasattr(part, "args"):
|
|
161
|
+
if isinstance(part.args, dict) and "file_path" in part.args:
|
|
162
|
+
files_modified.add(part.args["file_path"])
|
|
163
|
+
elif tool_name in ["run_command", "bash"] and hasattr(part, "args"):
|
|
164
|
+
if isinstance(part.args, dict) and "command" in part.args:
|
|
165
|
+
commands_run.append(part.args["command"])
|
|
166
|
+
|
|
167
|
+
if verbosity in ["normal", "detailed"]:
|
|
168
|
+
# Add what was attempted
|
|
169
|
+
if tool_calls_summary:
|
|
170
|
+
tool_counts: dict[str, int] = {}
|
|
171
|
+
for tool in tool_calls_summary:
|
|
172
|
+
tool_counts[tool] = tool_counts.get(tool, 0) + 1
|
|
173
|
+
|
|
174
|
+
fallback.issues.append(f"Executed {len(tool_calls_summary)} tool calls:")
|
|
175
|
+
for tool, count in sorted(tool_counts.items()):
|
|
176
|
+
fallback.issues.append(f" • {tool}: {count}x")
|
|
177
|
+
|
|
178
|
+
if verbosity == "detailed":
|
|
179
|
+
if files_modified:
|
|
180
|
+
fallback.issues.append(f"\nFiles modified ({len(files_modified)}):")
|
|
181
|
+
for f in sorted(files_modified)[:5]:
|
|
182
|
+
fallback.issues.append(f" • {f}")
|
|
183
|
+
if len(files_modified) > 5:
|
|
184
|
+
fallback.issues.append(f" • ... and {len(files_modified) - 5} more")
|
|
185
|
+
|
|
186
|
+
if commands_run:
|
|
187
|
+
fallback.issues.append(f"\nCommands executed ({len(commands_run)}):")
|
|
188
|
+
for cmd in commands_run[:3]:
|
|
189
|
+
display_cmd = cmd if len(cmd) <= 60 else cmd[:57] + "..."
|
|
190
|
+
fallback.issues.append(f" • {display_cmd}")
|
|
191
|
+
if len(commands_run) > 3:
|
|
192
|
+
fallback.issues.append(f" • ... and {len(commands_run) - 3} more")
|
|
193
|
+
|
|
194
|
+
# Add helpful next steps
|
|
195
|
+
fallback.next_steps.append("The task may be too complex - try breaking it into smaller steps")
|
|
196
|
+
fallback.next_steps.append("Check the output above for any errors or partial progress")
|
|
197
|
+
if files_modified:
|
|
198
|
+
fallback.next_steps.append("Review modified files to see what changes were made")
|
|
199
|
+
|
|
200
|
+
return fallback
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def format_fallback_output(fallback: FallbackResponse) -> str:
|
|
204
|
+
"""Format a fallback response into a comprehensive output string."""
|
|
205
|
+
output_parts = [fallback.summary, ""]
|
|
206
|
+
|
|
207
|
+
if fallback.progress:
|
|
208
|
+
output_parts.append(f"Progress: {fallback.progress}")
|
|
209
|
+
|
|
210
|
+
if fallback.issues:
|
|
211
|
+
output_parts.append("\nWhat happened:")
|
|
212
|
+
output_parts.extend(fallback.issues)
|
|
213
|
+
|
|
214
|
+
if fallback.next_steps:
|
|
215
|
+
output_parts.append("\nSuggested next steps:")
|
|
216
|
+
for step in fallback.next_steps:
|
|
217
|
+
output_parts.append(f" • {step}")
|
|
218
|
+
|
|
219
|
+
return "\n".join(output_parts)
|
|
@@ -6,10 +6,12 @@ from typing import Any, Awaitable, Callable, Optional, Tuple
|
|
|
6
6
|
from tunacode.core.logging.logger import get_logger
|
|
7
7
|
from tunacode.core.state import StateManager
|
|
8
8
|
from tunacode.types import UsageTrackerProtocol
|
|
9
|
+
from tunacode.ui.tool_descriptions import get_batch_description, get_tool_description
|
|
9
10
|
|
|
10
11
|
from .response_state import ResponseState
|
|
11
12
|
from .task_completion import check_task_completion
|
|
12
13
|
from .tool_buffer import ToolBuffer
|
|
14
|
+
from .truncation_checker import check_for_truncation
|
|
13
15
|
|
|
14
16
|
logger = get_logger(__name__)
|
|
15
17
|
|
|
@@ -171,7 +173,7 @@ async def _process_node(
|
|
|
171
173
|
# Check for truncation patterns
|
|
172
174
|
if all_content_parts:
|
|
173
175
|
combined_content = " ".join(all_content_parts).strip()
|
|
174
|
-
appears_truncated =
|
|
176
|
+
appears_truncated = check_for_truncation(combined_content)
|
|
175
177
|
|
|
176
178
|
# If we only got empty content and no tool calls, we should NOT consider this a valid response
|
|
177
179
|
# This prevents the agent from stopping when it gets empty responses
|
|
@@ -229,79 +231,6 @@ async def _process_node(
|
|
|
229
231
|
return False, None
|
|
230
232
|
|
|
231
233
|
|
|
232
|
-
def _check_for_truncation(combined_content: str) -> bool:
|
|
233
|
-
"""Check if content appears to be truncated."""
|
|
234
|
-
if not combined_content:
|
|
235
|
-
return False
|
|
236
|
-
|
|
237
|
-
# Truncation indicators:
|
|
238
|
-
# 1. Ends with "..." or "…" (but not part of a complete sentence)
|
|
239
|
-
# 2. Ends mid-word (no punctuation, space, or complete word)
|
|
240
|
-
# 3. Contains incomplete markdown/code blocks
|
|
241
|
-
# 4. Ends with incomplete parentheses/brackets
|
|
242
|
-
|
|
243
|
-
# Check for ellipsis at end suggesting truncation
|
|
244
|
-
if combined_content.endswith(("...", "…")) and not combined_content.endswith(("....", "….")):
|
|
245
|
-
return True
|
|
246
|
-
|
|
247
|
-
# Check for mid-word truncation (ends with letters but no punctuation)
|
|
248
|
-
if combined_content and combined_content[-1].isalpha():
|
|
249
|
-
# Look for incomplete words by checking if last "word" seems cut off
|
|
250
|
-
words = combined_content.split()
|
|
251
|
-
if words:
|
|
252
|
-
last_word = words[-1]
|
|
253
|
-
# Common complete word endings vs likely truncations
|
|
254
|
-
complete_endings = (
|
|
255
|
-
"ing",
|
|
256
|
-
"ed",
|
|
257
|
-
"ly",
|
|
258
|
-
"er",
|
|
259
|
-
"est",
|
|
260
|
-
"tion",
|
|
261
|
-
"ment",
|
|
262
|
-
"ness",
|
|
263
|
-
"ity",
|
|
264
|
-
"ous",
|
|
265
|
-
"ive",
|
|
266
|
-
"able",
|
|
267
|
-
"ible",
|
|
268
|
-
)
|
|
269
|
-
incomplete_patterns = (
|
|
270
|
-
"referen",
|
|
271
|
-
"inte",
|
|
272
|
-
"proces",
|
|
273
|
-
"analy",
|
|
274
|
-
"deve",
|
|
275
|
-
"imple",
|
|
276
|
-
"execu",
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
if any(last_word.lower().endswith(pattern) for pattern in incomplete_patterns):
|
|
280
|
-
return True
|
|
281
|
-
elif len(last_word) > 2 and not any(
|
|
282
|
-
last_word.lower().endswith(end) for end in complete_endings
|
|
283
|
-
):
|
|
284
|
-
# Likely truncated if doesn't end with common suffix
|
|
285
|
-
return True
|
|
286
|
-
|
|
287
|
-
# Check for unclosed markdown code blocks
|
|
288
|
-
code_block_count = combined_content.count("```")
|
|
289
|
-
if code_block_count % 2 != 0:
|
|
290
|
-
return True
|
|
291
|
-
|
|
292
|
-
# Check for unclosed brackets/parentheses (more opens than closes)
|
|
293
|
-
open_brackets = (
|
|
294
|
-
combined_content.count("[") + combined_content.count("(") + combined_content.count("{")
|
|
295
|
-
)
|
|
296
|
-
close_brackets = (
|
|
297
|
-
combined_content.count("]") + combined_content.count(")") + combined_content.count("}")
|
|
298
|
-
)
|
|
299
|
-
if open_brackets > close_brackets:
|
|
300
|
-
return True
|
|
301
|
-
|
|
302
|
-
return False
|
|
303
|
-
|
|
304
|
-
|
|
305
234
|
async def _display_raw_api_response(node: Any, ui: Any) -> None:
|
|
306
235
|
"""Display raw API response data when thoughts are enabled."""
|
|
307
236
|
|
|
@@ -382,6 +311,14 @@ async def _process_tool_calls(
|
|
|
382
311
|
if tool_buffer is not None and part.tool_name in READ_ONLY_TOOLS:
|
|
383
312
|
# Add to buffer instead of executing immediately
|
|
384
313
|
tool_buffer.add(part, node)
|
|
314
|
+
|
|
315
|
+
# Update spinner to show we're collecting tools
|
|
316
|
+
buffered_count = len(tool_buffer.read_only_tasks)
|
|
317
|
+
await ui.update_spinner_message(
|
|
318
|
+
f"[bold #00d7ff]Collecting tools ({buffered_count} buffered)...[/bold #00d7ff]",
|
|
319
|
+
state_manager,
|
|
320
|
+
)
|
|
321
|
+
|
|
385
322
|
if state_manager.session.show_thoughts:
|
|
386
323
|
await ui.muted(
|
|
387
324
|
f"⏸️ BUFFERED: {part.tool_name} (will execute in parallel batch)"
|
|
@@ -399,6 +336,13 @@ async def _process_tool_calls(
|
|
|
399
336
|
|
|
400
337
|
start_time = time.time()
|
|
401
338
|
|
|
339
|
+
# Update spinner message for batch execution
|
|
340
|
+
tool_names = [part.tool_name for part, _ in buffered_tasks]
|
|
341
|
+
batch_msg = get_batch_description(len(buffered_tasks), tool_names)
|
|
342
|
+
await ui.update_spinner_message(
|
|
343
|
+
f"[bold #00d7ff]{batch_msg}...[/bold #00d7ff]", state_manager
|
|
344
|
+
)
|
|
345
|
+
|
|
402
346
|
# Enhanced visual feedback for parallel execution
|
|
403
347
|
await ui.muted("\n" + "=" * 60)
|
|
404
348
|
await ui.muted(
|
|
@@ -452,9 +396,30 @@ async def _process_tool_calls(
|
|
|
452
396
|
f"(~{speedup:.1f}x faster than sequential)\n"
|
|
453
397
|
)
|
|
454
398
|
|
|
399
|
+
# Reset spinner message back to thinking
|
|
400
|
+
from tunacode.constants import UI_THINKING_MESSAGE
|
|
401
|
+
|
|
402
|
+
await ui.update_spinner_message(UI_THINKING_MESSAGE, state_manager)
|
|
403
|
+
|
|
455
404
|
# Now execute the write/execute tool
|
|
456
405
|
if state_manager.session.show_thoughts:
|
|
457
406
|
await ui.warning(f"⚠️ SEQUENTIAL: {part.tool_name} (write/execute tool)")
|
|
407
|
+
|
|
408
|
+
# Update spinner for sequential tool
|
|
409
|
+
tool_args = getattr(part, "args", {}) if hasattr(part, "args") else {}
|
|
410
|
+
# Parse args if they're a JSON string
|
|
411
|
+
if isinstance(tool_args, str):
|
|
412
|
+
import json
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
tool_args = json.loads(tool_args)
|
|
416
|
+
except (json.JSONDecodeError, TypeError):
|
|
417
|
+
tool_args = {}
|
|
418
|
+
tool_desc = get_tool_description(part.tool_name, tool_args)
|
|
419
|
+
await ui.update_spinner_message(
|
|
420
|
+
f"[bold #00d7ff]{tool_desc}...[/bold #00d7ff]", state_manager
|
|
421
|
+
)
|
|
422
|
+
|
|
458
423
|
await tool_callback(part, node)
|
|
459
424
|
|
|
460
425
|
# Track tool calls in session
|