tunacode-cli 0.0.47__tar.gz → 0.0.49__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.47/src/tunacode_cli.egg-info → tunacode_cli-0.0.49}/PKG-INFO +2 -2
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/README.md +1 -1
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/pyproject.toml +34 -1
- tunacode_cli-0.0.49/src/api/auth.py +13 -0
- tunacode_cli-0.0.49/src/api/users.py +8 -0
- tunacode_cli-0.0.49/src/tunacode/__init__.py +4 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/repl.py +28 -2
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/constants.py +2 -1
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/agents/main.py +33 -84
- tunacode_cli-0.0.49/src/tunacode/core/logging/__init__.py +29 -0
- tunacode_cli-0.0.49/src/tunacode/core/logging/config.py +28 -0
- tunacode_cli-0.0.49/src/tunacode/core/logging/formatters.py +48 -0
- tunacode_cli-0.0.49/src/tunacode/core/logging/handlers.py +83 -0
- tunacode_cli-0.0.49/src/tunacode/core/logging/logger.py +8 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/hierarchy.py +2 -1
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/state.py +4 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/base.py +7 -1
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/types.py +1 -1
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/completers.py +2 -2
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/console.py +30 -9
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/input.py +1 -1
- tunacode_cli-0.0.49/src/tunacode/ui/keybindings.py +82 -0
- tunacode_cli-0.0.49/src/tunacode/ui/logging_compat.py +44 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/output.py +7 -6
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/panels.py +28 -10
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/security.py +3 -2
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49/src/tunacode_cli.egg-info}/PKG-INFO +2 -2
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/SOURCES.txt +8 -60
- tunacode_cli-0.0.49/src/tunacode_cli.egg-info/top_level.txt +3 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_tunacode_logging.py +10 -15
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_error_handling.py +12 -4
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_input_handling.py +6 -4
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_multiline_input.py +11 -4
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_output_display_logic.py +1 -1
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_async_ui.py +4 -4
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/conftest.py +4 -2
- tunacode_cli-0.0.47/src/tunacode/ui/keybindings.py +0 -41
- tunacode_cli-0.0.47/src/tunacode/utils/__init__.py +0 -0
- tunacode_cli-0.0.47/src/tunacode_cli.egg-info/top_level.txt +0 -1
- tunacode_cli-0.0.47/tests/crud/test_core_file_operations.py +0 -452
- tunacode_cli-0.0.47/tests/fixtures/__init__.py +0 -1
- tunacode_cli-0.0.47/tests/fixtures/file_operations.py +0 -332
- tunacode_cli-0.0.47/tests/integration/test_error_recovery_flow.py +0 -43
- tunacode_cli-0.0.47/tests/integration/test_full_session_flow.py +0 -68
- tunacode_cli-0.0.47/tests/integration/test_mcp_tool_flow.py +0 -51
- tunacode_cli-0.0.47/tests/integration/test_multi_tool_operations.py +0 -61
- tunacode_cli-0.0.47/tests/integration/test_performance_scenarios.py +0 -62
- tunacode_cli-0.0.47/tests/integration/test_usage_tracking_integration.py +0 -134
- tunacode_cli-0.0.47/tests/test_actual_parallelism.py +0 -260
- tunacode_cli-0.0.47/tests/test_agent_initialization.py +0 -152
- tunacode_cli-0.0.47/tests/test_api_response_parser.py +0 -82
- tunacode_cli-0.0.47/tests/test_background_manager.py +0 -27
- tunacode_cli-0.0.47/tests/test_characterization_agent_main.py +0 -57
- tunacode_cli-0.0.47/tests/test_characterization_bash.py +0 -227
- tunacode_cli-0.0.47/tests/test_characterization_commands_system.py +0 -90
- tunacode_cli-0.0.47/tests/test_characterization_glob.py +0 -395
- tunacode_cli-0.0.47/tests/test_characterization_grep.py +0 -329
- tunacode_cli-0.0.47/tests/test_characterization_grep_performance.py +0 -296
- tunacode_cli-0.0.47/tests/test_characterization_iteration_limits.py +0 -141
- tunacode_cli-0.0.47/tests/test_characterization_list_dir.py +0 -289
- tunacode_cli-0.0.47/tests/test_characterization_read_file.py +0 -201
- tunacode_cli-0.0.47/tests/test_characterization_repl_utils.py +0 -41
- tunacode_cli-0.0.47/tests/test_characterization_run_command.py +0 -241
- tunacode_cli-0.0.47/tests/test_characterization_setup_system.py +0 -76
- tunacode_cli-0.0.47/tests/test_characterization_tool_ui_behavior.py +0 -381
- tunacode_cli-0.0.47/tests/test_characterization_update_file.py +0 -231
- tunacode_cli-0.0.47/tests/test_characterization_utilities.py +0 -64
- tunacode_cli-0.0.47/tests/test_characterization_write_file.py +0 -224
- tunacode_cli-0.0.47/tests/test_cli_command_flow.py +0 -312
- tunacode_cli-0.0.47/tests/test_cli_file_operations_integration.py +0 -283
- tunacode_cli-0.0.47/tests/test_config_directory_creation.py +0 -105
- tunacode_cli-0.0.47/tests/test_config_setup_async.py +0 -120
- tunacode_cli-0.0.47/tests/test_cost_calculator.py +0 -32
- tunacode_cli-0.0.47/tests/test_enhanced_visual_feedback.py +0 -95
- tunacode_cli-0.0.47/tests/test_fallback_responses.py +0 -48
- tunacode_cli-0.0.47/tests/test_fast_glob_search.py +0 -129
- tunacode_cli-0.0.47/tests/test_file_operations_edge_cases.py +0 -393
- tunacode_cli-0.0.47/tests/test_file_operations_stress.py +0 -333
- tunacode_cli-0.0.47/tests/test_file_reference_context_tracking.py +0 -146
- tunacode_cli-0.0.47/tests/test_file_reference_expansion.py +0 -208
- tunacode_cli-0.0.47/tests/test_grep_fast_glob.py +0 -380
- tunacode_cli-0.0.47/tests/test_grep_legacy_compat.py +0 -61
- tunacode_cli-0.0.47/tests/test_grep_timeout.py +0 -180
- tunacode_cli-0.0.47/tests/test_json_tool_parsing.py +0 -214
- tunacode_cli-0.0.47/tests/test_list_dir.py +0 -188
- tunacode_cli-0.0.47/tests/test_parallel_execution_demo.py +0 -151
- tunacode_cli-0.0.47/tests/test_parallel_execution_freeze_fix.py +0 -153
- tunacode_cli-0.0.47/tests/test_parallel_execution_integration.py +0 -202
- tunacode_cli-0.0.47/tests/test_parallel_read_only_tools.py +0 -248
- tunacode_cli-0.0.47/tests/test_parallel_tool_execution.py +0 -151
- tunacode_cli-0.0.47/tests/test_read_only_confirmation.py +0 -67
- tunacode_cli-0.0.47/tests/test_streaming_panel_tool_confirmation.py +0 -176
- tunacode_cli-0.0.47/tests/test_streaming_spinner_conflict.py +0 -117
- tunacode_cli-0.0.47/tests/test_todo_functionality.py +0 -441
- tunacode_cli-0.0.47/tests/test_tool_categorization.py +0 -110
- tunacode_cli-0.0.47/tests/test_tool_combinations.py +0 -575
- tunacode_cli-0.0.47/tests/test_tool_handler_ui_messages.py +0 -113
- tunacode_cli-0.0.47/tests/test_update_command.py +0 -48
- tunacode_cli-0.0.47/tests/test_visual_parallel_feedback.py +0 -172
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/CLAUDE.md +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/LICENSE +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/MANIFEST.in +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/TUNACODE.md +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/setup.cfg +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/setup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/base.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/conversation.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/debug.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/development.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/model.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/system.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/todo.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/registry.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/main.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/defaults.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/models.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/settings.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/context.py +0 -0
- {tunacode_cli-0.0.47/src/tunacode → tunacode_cli-0.0.49/src/tunacode/core}/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/agents/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/agents/utils.py +0 -0
- {tunacode_cli-0.0.47/src/tunacode/core → tunacode_cli-0.0.49/src/tunacode/core/background}/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/background/manager.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/code_index.py +0 -0
- {tunacode_cli-0.0.47/src/tunacode/core/background → tunacode_cli-0.0.49/src/tunacode/core/llm}/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/aggregator.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/budget.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/decomposer.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/executor.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/agent_setup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/base.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/config_setup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/coordinator.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/environment_setup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/git_safety_setup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/token_usage/api_response_parser.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/token_usage/cost_calculator.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/token_usage/usage_tracker.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/tool_handler.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/exceptions.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/prompts/system.md +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/py.typed +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/services/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/services/mcp.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/setup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/bash.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/glob.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/grep.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/list_dir.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/read_file.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/read_file_async_poc.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/run_command.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/todo.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/update_file.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/write_file.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/constants.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/decorators.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/lexers.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/prompt_manager.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/recursive_progress.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/tool_ui.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/utils.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/validators.py +0 -0
- {tunacode_cli-0.0.47/src/tunacode/core/llm → tunacode_cli-0.0.49/src/tunacode/utils}/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/bm25.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/diff_utils.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/file_utils.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/import_cache.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/message_utils.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/retry.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/ripgrep.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/system.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/text_utils.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/token_counter.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/user_configuration.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/requires.txt +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/conftest.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_agent_creation.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_process_node.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_process_request.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_tool_message_patching.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_background_edge_cases.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_cleanup.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_task_cancellation.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_task_creation.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_task_execution.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_cache_management.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_file_scanning.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_index_building.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_search_operations.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/commands/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/commands/test_init_command.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/conftest.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/__init__.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_context_acceptance.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_context_integration.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_context_loading.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_command_parsing.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_repl_initialization.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_session_flow.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_error_recovery.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_llm_routing.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_mcp_integration.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_service_lifecycle.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_agent_tracking.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_message_history.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_permissions.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_session_management.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_state_initialization.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_user_config.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/test_characterization_commands.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_console_output.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_diff_display.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_prompt_rendering.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_tool_confirmations.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/conftest.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_expand_file_refs.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_file_operations.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_git_commands.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_token_counting.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_agent_output_formatting.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_json_retry.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_prompt_changes_validation.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_security.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_tool_batching_retry.py +0 -0
- {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/unit/test_recursive_executor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.49
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License: MIT
|
|
@@ -37,7 +37,7 @@ Requires-Dist: textual-dev; extra == "dev"
|
|
|
37
37
|
Requires-Dist: pre-commit; extra == "dev"
|
|
38
38
|
Dynamic: license-file
|
|
39
39
|
|
|
40
|
-
# TunaCode
|
|
40
|
+
# TunaCode CLI
|
|
41
41
|
|
|
42
42
|
<div align="center">
|
|
43
43
|
|
|
@@ -4,7 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tunacode-cli"
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
version = "0.0.49"
|
|
8
9
|
description = "Your agentic CLI developer."
|
|
9
10
|
keywords = ["cli", "agent", "development", "automation"]
|
|
10
11
|
readme = "README.md"
|
|
@@ -77,3 +78,35 @@ exclude = [
|
|
|
77
78
|
"dist",
|
|
78
79
|
"venv",
|
|
79
80
|
]
|
|
81
|
+
|
|
82
|
+
[tool.vulture]
|
|
83
|
+
min_confidence = 80
|
|
84
|
+
paths = ["src", "tests"]
|
|
85
|
+
exclude = ["venv/", "build/", "dist/", ".git/", "__pycache__/", "*.egg-info/"]
|
|
86
|
+
# Whitelist of false positives
|
|
87
|
+
ignore_names = [
|
|
88
|
+
# TYPE_CHECKING imports
|
|
89
|
+
"ReadStream",
|
|
90
|
+
"WriteStream",
|
|
91
|
+
"StateManager",
|
|
92
|
+
"ModelRequest",
|
|
93
|
+
"CommandRegistry",
|
|
94
|
+
# Pytest fixtures
|
|
95
|
+
"caplog",
|
|
96
|
+
"temp_workspace",
|
|
97
|
+
"setup_test_environment",
|
|
98
|
+
"excinfo",
|
|
99
|
+
# Common patterns
|
|
100
|
+
"_complete_event", # prompt_toolkit API requirement
|
|
101
|
+
"kw", # **kw pattern
|
|
102
|
+
"kwargs", # **kwargs pattern
|
|
103
|
+
"message", # exception handling
|
|
104
|
+
"response_obj", # API responses
|
|
105
|
+
# Test parameters and mock arguments
|
|
106
|
+
"should_restart", # test parameter
|
|
107
|
+
"exc", # exception handling in __aexit__
|
|
108
|
+
"exc_type", # exception handling in __aexit__
|
|
109
|
+
"tb", # traceback in __aexit__
|
|
110
|
+
"style_dict", # Style.from_dict parameter
|
|
111
|
+
"style_str", # get_attrs_for_style_str parameter
|
|
112
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def authenticate(username, password):
|
|
5
|
+
# TODO: Add password hashing
|
|
6
|
+
if username == "admin" and password == "admin":
|
|
7
|
+
return generate_token(username)
|
|
8
|
+
return None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_token(username):
|
|
12
|
+
# TODO: Add expiration
|
|
13
|
+
return jwt.encode({"user": username}, "secret")
|
|
@@ -90,6 +90,12 @@ def _parse_args(args) -> ToolArgs:
|
|
|
90
90
|
|
|
91
91
|
async def _tool_handler(part, state_manager: StateManager):
|
|
92
92
|
"""Handle tool execution with separated business logic and UI."""
|
|
93
|
+
# Check for cancellation before tool execution (only if explicitly set to True)
|
|
94
|
+
operation_cancelled = getattr(state_manager.session, "operation_cancelled", False)
|
|
95
|
+
if operation_cancelled is True:
|
|
96
|
+
logger.debug("Tool execution cancelled")
|
|
97
|
+
raise CancelledError("Operation was cancelled")
|
|
98
|
+
|
|
93
99
|
tool_handler = ToolHandler(state_manager)
|
|
94
100
|
|
|
95
101
|
if tool_handler.should_confirm(part.tool_name):
|
|
@@ -249,6 +255,12 @@ async def _display_agent_output(res, enable_streaming: bool) -> None:
|
|
|
249
255
|
async def process_request(text: str, state_manager: StateManager, output: bool = True):
|
|
250
256
|
"""Process input using the agent, handling cancellation safely."""
|
|
251
257
|
|
|
258
|
+
# Check for cancellation before starting (only if explicitly set to True)
|
|
259
|
+
operation_cancelled = getattr(state_manager.session, "operation_cancelled", False)
|
|
260
|
+
if operation_cancelled is True:
|
|
261
|
+
logger.debug("Operation cancelled before processing started")
|
|
262
|
+
raise CancelledError("Operation was cancelled")
|
|
263
|
+
|
|
252
264
|
state_manager.session.spinner = await ui.spinner(
|
|
253
265
|
True, state_manager.session.spinner, state_manager
|
|
254
266
|
)
|
|
@@ -275,6 +287,12 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
275
287
|
await ui.error(str(e))
|
|
276
288
|
return
|
|
277
289
|
|
|
290
|
+
# Check for cancellation before proceeding with agent call (only if explicitly set to True)
|
|
291
|
+
operation_cancelled = getattr(state_manager.session, "operation_cancelled", False)
|
|
292
|
+
if operation_cancelled is True:
|
|
293
|
+
logger.debug("Operation cancelled before agent processing")
|
|
294
|
+
raise CancelledError("Operation was cancelled")
|
|
295
|
+
|
|
278
296
|
enable_streaming = state_manager.session.user_config.get("settings", {}).get(
|
|
279
297
|
"enable_streaming", True
|
|
280
298
|
)
|
|
@@ -338,13 +356,13 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
338
356
|
# Always show files in context after agent response
|
|
339
357
|
if state_manager.session.files_in_context:
|
|
340
358
|
filenames = [Path(f).name for f in sorted(state_manager.session.files_in_context)]
|
|
341
|
-
await ui.muted(f"
|
|
359
|
+
await ui.muted(f"Files in context: {', '.join(filenames)}")
|
|
342
360
|
|
|
343
361
|
# --- ERROR HANDLING ---
|
|
344
362
|
except CancelledError:
|
|
345
363
|
await ui.muted(MSG_REQUEST_CANCELLED)
|
|
346
364
|
except UserAbortError:
|
|
347
|
-
await ui.muted(
|
|
365
|
+
await ui.muted(MSG_OPERATION_ABORTED)
|
|
348
366
|
except UnexpectedModelBehavior as e:
|
|
349
367
|
error_message = str(e)
|
|
350
368
|
await ui.muted(error_message)
|
|
@@ -360,6 +378,9 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
360
378
|
finally:
|
|
361
379
|
await ui.spinner(False, state_manager.session.spinner, state_manager)
|
|
362
380
|
state_manager.session.current_task = None
|
|
381
|
+
# Reset cancellation flag when task completes (if attribute exists)
|
|
382
|
+
if hasattr(state_manager.session, "operation_cancelled"):
|
|
383
|
+
state_manager.session.operation_cancelled = False
|
|
363
384
|
|
|
364
385
|
if "multiline" in state_manager.session.input_sessions:
|
|
365
386
|
await run_in_terminal(
|
|
@@ -460,9 +481,14 @@ async def repl(state_manager: StateManager):
|
|
|
460
481
|
await ui.muted(MSG_AGENT_BUSY)
|
|
461
482
|
continue
|
|
462
483
|
|
|
484
|
+
# Reset cancellation flag for new operations (if attribute exists)
|
|
485
|
+
if hasattr(state_manager.session, "operation_cancelled"):
|
|
486
|
+
state_manager.session.operation_cancelled = False
|
|
487
|
+
|
|
463
488
|
state_manager.session.current_task = get_app().create_background_task(
|
|
464
489
|
process_request(line, state_manager)
|
|
465
490
|
)
|
|
491
|
+
await state_manager.session.current_task
|
|
466
492
|
|
|
467
493
|
state_manager.session.update_token_count()
|
|
468
494
|
context_display = get_context_window_display(
|
|
@@ -6,7 +6,6 @@ Handles agent creation, configuration, and request processing.
|
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import json
|
|
9
|
-
import logging
|
|
10
9
|
import os
|
|
11
10
|
import re
|
|
12
11
|
from datetime import datetime, timezone
|
|
@@ -15,6 +14,8 @@ from typing import Any, Iterator, List, Optional, Tuple
|
|
|
15
14
|
|
|
16
15
|
from pydantic_ai import Agent
|
|
17
16
|
|
|
17
|
+
from tunacode.core.logging.logger import get_logger
|
|
18
|
+
|
|
18
19
|
# Import streaming types with fallback for older versions
|
|
19
20
|
try:
|
|
20
21
|
from pydantic_ai.messages import (
|
|
@@ -30,7 +31,6 @@ except ImportError:
|
|
|
30
31
|
STREAMING_AVAILABLE = False
|
|
31
32
|
|
|
32
33
|
from tunacode.constants import READ_ONLY_TOOLS
|
|
33
|
-
from tunacode.core.recursive import RecursiveTaskExecutor
|
|
34
34
|
from tunacode.core.state import StateManager
|
|
35
35
|
from tunacode.core.token_usage.api_response_parser import ApiResponseParser
|
|
36
36
|
from tunacode.core.token_usage.cost_calculator import CostCalculator
|
|
@@ -60,7 +60,7 @@ from tunacode.types import (
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
# Configure logging
|
|
63
|
-
logger =
|
|
63
|
+
logger = get_logger(__name__)
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
class ToolBuffer:
|
|
@@ -528,12 +528,12 @@ def get_or_create_agent(model: ModelName, state_manager: StateManager) -> Pydant
|
|
|
528
528
|
tunacode_content = tunacode_path.read_text(encoding="utf-8")
|
|
529
529
|
if tunacode_content.strip():
|
|
530
530
|
# Log that we found TUNACODE.md
|
|
531
|
-
|
|
531
|
+
logger.info("📄 TUNACODE.md located: Loading context...")
|
|
532
532
|
|
|
533
533
|
system_prompt += "\n\n# Project Context from TUNACODE.md\n" + tunacode_content
|
|
534
534
|
else:
|
|
535
535
|
# Log that TUNACODE.md was not found
|
|
536
|
-
|
|
536
|
+
logger.info("📄 TUNACODE.md not found: Using default context")
|
|
537
537
|
except Exception as e:
|
|
538
538
|
# Log errors loading TUNACODE.md at debug level
|
|
539
539
|
logger.debug(f"Error loading TUNACODE.md: {e}")
|
|
@@ -547,9 +547,8 @@ def get_or_create_agent(model: ModelName, state_manager: StateManager) -> Pydant
|
|
|
547
547
|
system_prompt += f'\n\n# Current Todo List\n\nYou have existing todos that need attention:\n\n{current_todos}\n\nRemember to check progress on these todos and update them as you work. Use todo("list") to see current status anytime.'
|
|
548
548
|
except Exception as e:
|
|
549
549
|
# Log error but don't fail agent creation
|
|
550
|
-
import sys
|
|
551
550
|
|
|
552
|
-
|
|
551
|
+
logger.warning(f"Warning: Failed to load todos: {e}")
|
|
553
552
|
|
|
554
553
|
state_manager.session.agents[model] = Agent(
|
|
555
554
|
model=model,
|
|
@@ -763,55 +762,6 @@ async def process_request(
|
|
|
763
762
|
"fallback_response", True
|
|
764
763
|
)
|
|
765
764
|
|
|
766
|
-
# Check if recursive execution is enabled
|
|
767
|
-
use_recursive = state_manager.session.user_config.get("settings", {}).get(
|
|
768
|
-
"use_recursive_execution", True
|
|
769
|
-
)
|
|
770
|
-
recursive_threshold = state_manager.session.user_config.get("settings", {}).get(
|
|
771
|
-
"recursive_complexity_threshold", 0.7
|
|
772
|
-
)
|
|
773
|
-
|
|
774
|
-
# Check if recursive execution should be used
|
|
775
|
-
if use_recursive and state_manager.session.current_recursion_depth == 0:
|
|
776
|
-
try:
|
|
777
|
-
# Initialize recursive executor
|
|
778
|
-
recursive_executor = RecursiveTaskExecutor(
|
|
779
|
-
state_manager=state_manager,
|
|
780
|
-
max_depth=state_manager.session.max_recursion_depth,
|
|
781
|
-
min_complexity_threshold=recursive_threshold,
|
|
782
|
-
default_iteration_budget=max_iterations,
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
# Analyze task complexity
|
|
786
|
-
complexity_result = await recursive_executor.decomposer.analyze_and_decompose(
|
|
787
|
-
message
|
|
788
|
-
)
|
|
789
|
-
|
|
790
|
-
if (
|
|
791
|
-
complexity_result.should_decompose
|
|
792
|
-
and complexity_result.total_complexity >= recursive_threshold
|
|
793
|
-
):
|
|
794
|
-
if state_manager.session.show_thoughts:
|
|
795
|
-
from tunacode.ui import console as ui
|
|
796
|
-
|
|
797
|
-
await ui.muted(
|
|
798
|
-
f"\n🔄 RECURSIVE EXECUTION: Task complexity {complexity_result.total_complexity:.2f} >= {recursive_threshold}"
|
|
799
|
-
)
|
|
800
|
-
await ui.muted(f"Reasoning: {complexity_result.reasoning}")
|
|
801
|
-
await ui.muted(f"Subtasks: {len(complexity_result.subtasks)}")
|
|
802
|
-
|
|
803
|
-
# Execute recursively
|
|
804
|
-
success, result, error = await recursive_executor.execute_task(
|
|
805
|
-
request=message, parent_task_id=None, depth=0
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
# For now, fall back to normal execution
|
|
809
|
-
# TODO: Properly integrate recursive execution results
|
|
810
|
-
pass
|
|
811
|
-
except Exception as e:
|
|
812
|
-
logger.warning(f"Recursive execution failed, falling back to normal: {e}")
|
|
813
|
-
# Continue with normal execution
|
|
814
|
-
|
|
815
765
|
from tunacode.configuration.models import ModelRegistry
|
|
816
766
|
from tunacode.core.token_usage.usage_tracker import UsageTracker
|
|
817
767
|
|
|
@@ -910,28 +860,27 @@ async def process_request(
|
|
|
910
860
|
buffered_tasks = tool_buffer.flush()
|
|
911
861
|
start_time = time.time()
|
|
912
862
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
await ui.muted("=" * 60)
|
|
863
|
+
await ui.muted("\n" + "=" * 60)
|
|
864
|
+
await ui.muted(
|
|
865
|
+
f"🚀 FINAL BATCH: Executing {len(buffered_tasks)} buffered read-only tools"
|
|
866
|
+
)
|
|
867
|
+
await ui.muted("=" * 60)
|
|
919
868
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
869
|
+
for idx, (part, node) in enumerate(buffered_tasks, 1):
|
|
870
|
+
tool_desc = f" [{idx}] {part.tool_name}"
|
|
871
|
+
if hasattr(part, "args") and isinstance(part.args, dict):
|
|
872
|
+
if part.tool_name == "read_file" and "file_path" in part.args:
|
|
873
|
+
tool_desc += f" → {part.args['file_path']}"
|
|
874
|
+
elif part.tool_name == "grep" and "pattern" in part.args:
|
|
875
|
+
tool_desc += f" → pattern: '{part.args['pattern']}'"
|
|
876
|
+
if "include_files" in part.args:
|
|
877
|
+
tool_desc += f", files: '{part.args['include_files']}'"
|
|
878
|
+
elif part.tool_name == "list_dir" and "directory" in part.args:
|
|
879
|
+
tool_desc += f" → {part.args['directory']}"
|
|
880
|
+
elif part.tool_name == "glob" and "pattern" in part.args:
|
|
881
|
+
tool_desc += f" → pattern: '{part.args['pattern']}'"
|
|
882
|
+
await ui.muted(tool_desc)
|
|
883
|
+
await ui.muted("=" * 60)
|
|
935
884
|
|
|
936
885
|
await execute_tools_parallel(buffered_tasks, tool_callback)
|
|
937
886
|
|
|
@@ -939,11 +888,10 @@ async def process_request(
|
|
|
939
888
|
sequential_estimate = len(buffered_tasks) * 100
|
|
940
889
|
speedup = sequential_estimate / elapsed_time if elapsed_time > 0 else 1.0
|
|
941
890
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
)
|
|
891
|
+
await ui.muted(
|
|
892
|
+
f"✅ Final batch completed in {elapsed_time:.0f}ms "
|
|
893
|
+
f"(~{speedup:.1f}x faster than sequential)\n"
|
|
894
|
+
)
|
|
947
895
|
|
|
948
896
|
# If we need to add a fallback response, create a wrapper
|
|
949
897
|
if not response_state.has_user_response and i >= max_iterations and fallback_enabled:
|
|
@@ -1089,6 +1037,7 @@ async def process_request(
|
|
|
1089
1037
|
f"'{type(self).__name__}' object has no attribute '{name}'"
|
|
1090
1038
|
)
|
|
1091
1039
|
|
|
1092
|
-
|
|
1040
|
+
return AgentRunWithState(agent_run)
|
|
1093
1041
|
except asyncio.CancelledError:
|
|
1094
|
-
raise UserAbortError
|
|
1042
|
+
# When task is cancelled, raise UserAbortError instead
|
|
1043
|
+
raise UserAbortError("Operation was cancelled by user")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
# Custom log level: THOUGHT
|
|
4
|
+
THOUGHT = 25
|
|
5
|
+
logging.addLevelName(THOUGHT, "THOUGHT")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def thought(self, message, *args, **kwargs):
|
|
9
|
+
if self.isEnabledFor(THOUGHT):
|
|
10
|
+
self._log(THOUGHT, message, args, **kwargs)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logging.Logger.thought = thought
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# RichHandler for UI output (stub, real implementation in handlers.py)
|
|
17
|
+
class RichHandler(logging.Handler):
|
|
18
|
+
def emit(self, record):
|
|
19
|
+
# Actual implementation in handlers.py
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def setup_logging(config_path=None):
|
|
24
|
+
"""
|
|
25
|
+
Set up logging configuration from YAML file.
|
|
26
|
+
"""
|
|
27
|
+
from .config import LogConfig
|
|
28
|
+
|
|
29
|
+
LogConfig.load(config_path)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import logging.config
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
DEFAULT_CONFIG_PATH = os.path.join(
|
|
8
|
+
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "config", "logging.yaml"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LogConfig:
|
|
13
|
+
@staticmethod
|
|
14
|
+
def load(config_path=None):
|
|
15
|
+
"""
|
|
16
|
+
Load logging configuration from YAML file and apply it.
|
|
17
|
+
"""
|
|
18
|
+
path = config_path or DEFAULT_CONFIG_PATH
|
|
19
|
+
if not os.path.exists(path):
|
|
20
|
+
raise FileNotFoundError(f"Logging config file not found: {path}")
|
|
21
|
+
with open(path, "r") as f:
|
|
22
|
+
config = yaml.safe_load(f)
|
|
23
|
+
logging_config = config.get("logging", config)
|
|
24
|
+
try:
|
|
25
|
+
logging.config.dictConfig(logging_config)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(f"Failed to configure logging: {e}")
|
|
28
|
+
logging.basicConfig(level=logging.INFO)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SimpleFormatter(logging.Formatter):
|
|
5
|
+
"""
|
|
6
|
+
Simple formatter for UI output.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__("[%(levelname)s] %(message)s")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DetailedFormatter(logging.Formatter):
|
|
14
|
+
"""
|
|
15
|
+
Detailed formatter for backend text logs.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__("[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)d] - %(message)s")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from pythonjsonlogger import jsonlogger
|
|
24
|
+
|
|
25
|
+
class JSONFormatter(jsonlogger.JsonFormatter):
|
|
26
|
+
"""
|
|
27
|
+
JSON formatter for structured logs.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
super().__init__("%(asctime)s %(name)s %(levelname)s %(message)s")
|
|
32
|
+
except ImportError:
|
|
33
|
+
import json
|
|
34
|
+
|
|
35
|
+
class JSONFormatter(logging.Formatter):
|
|
36
|
+
"""
|
|
37
|
+
Fallback JSON formatter if pythonjsonlogger is not installed.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def format(self, record):
|
|
41
|
+
log_entry = {
|
|
42
|
+
"timestamp": self.formatTime(record),
|
|
43
|
+
"level": record.levelname,
|
|
44
|
+
"name": record.name,
|
|
45
|
+
"line": record.lineno,
|
|
46
|
+
"message": record.getMessage(),
|
|
47
|
+
}
|
|
48
|
+
return json.dumps(log_entry)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.text import Text
|
|
6
|
+
|
|
7
|
+
# Global context for streaming state
|
|
8
|
+
_streaming_context = {"just_finished": False}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RichHandler(logging.Handler):
|
|
12
|
+
"""
|
|
13
|
+
Handler that outputs logs to the console using rich formatting.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
level_icons = {
|
|
17
|
+
"INFO": "",
|
|
18
|
+
"WARNING": "⚠️",
|
|
19
|
+
"ERROR": "❌",
|
|
20
|
+
"CRITICAL": "🚨",
|
|
21
|
+
"THOUGHT": "🤔",
|
|
22
|
+
"DEBUG": "",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def __init__(self, level=logging.NOTSET):
|
|
26
|
+
super().__init__(level)
|
|
27
|
+
self.console = Console()
|
|
28
|
+
|
|
29
|
+
def emit(self, record):
|
|
30
|
+
try:
|
|
31
|
+
icon = self.level_icons.get(record.levelname, "")
|
|
32
|
+
timestamp = self.formatTime(record)
|
|
33
|
+
msg = self.format(record)
|
|
34
|
+
if icon:
|
|
35
|
+
output = f"[{timestamp}] {icon} {msg}"
|
|
36
|
+
else:
|
|
37
|
+
output = f"[{timestamp}] {msg}"
|
|
38
|
+
|
|
39
|
+
# Check if we just finished streaming to avoid extra newlines
|
|
40
|
+
just_finished_streaming = _streaming_context.get("just_finished", False)
|
|
41
|
+
if just_finished_streaming:
|
|
42
|
+
_streaming_context["just_finished"] = False # Reset after use
|
|
43
|
+
# Don't add extra newline when transitioning from streaming
|
|
44
|
+
self.console.print(Text(output), end="\n")
|
|
45
|
+
else:
|
|
46
|
+
self.console.print(Text(output))
|
|
47
|
+
except Exception:
|
|
48
|
+
self.handleError(record)
|
|
49
|
+
|
|
50
|
+
def formatTime(self, record, datefmt=None):
|
|
51
|
+
from datetime import datetime
|
|
52
|
+
|
|
53
|
+
ct = datetime.fromtimestamp(record.created)
|
|
54
|
+
if datefmt:
|
|
55
|
+
return ct.strftime(datefmt)
|
|
56
|
+
return ct.strftime("%Y-%m-%d %H:%M:%S")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class StructuredFileHandler(logging.FileHandler):
|
|
60
|
+
"""
|
|
61
|
+
Handler that outputs logs as structured JSON lines.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def emit(self, record):
|
|
65
|
+
try:
|
|
66
|
+
log_entry = {
|
|
67
|
+
"timestamp": self.formatTime(record),
|
|
68
|
+
"level": record.levelname,
|
|
69
|
+
"name": record.name,
|
|
70
|
+
"line": record.lineno,
|
|
71
|
+
"message": record.getMessage(),
|
|
72
|
+
"extra_data": getattr(record, "extra", {}),
|
|
73
|
+
}
|
|
74
|
+
self.stream.write(json.dumps(log_entry) + "\n")
|
|
75
|
+
self.flush()
|
|
76
|
+
except Exception:
|
|
77
|
+
self.handleError(record)
|
|
78
|
+
|
|
79
|
+
def formatTime(self, record, datefmt=None):
|
|
80
|
+
from datetime import datetime, timezone
|
|
81
|
+
|
|
82
|
+
ct = datetime.fromtimestamp(record.created, tz=timezone.utc)
|
|
83
|
+
return ct.isoformat()
|
|
@@ -332,10 +332,11 @@ class TaskHierarchy:
|
|
|
332
332
|
"""Propagate context from one task to another.
|
|
333
333
|
|
|
334
334
|
Args:
|
|
335
|
-
from_task: Source task ID
|
|
335
|
+
from_task: Source task ID (unused, kept for API consistency)
|
|
336
336
|
to_task: Target task ID
|
|
337
337
|
context_update: Context to propagate
|
|
338
338
|
"""
|
|
339
|
+
_ = from_task # Unused but kept for API consistency
|
|
339
340
|
if to_task in self._execution_contexts:
|
|
340
341
|
self._execution_contexts[to_task].inherited_context.update(context_update)
|
|
341
342
|
|
|
@@ -41,6 +41,10 @@ class SessionState:
|
|
|
41
41
|
input_sessions: InputSessions = field(default_factory=dict)
|
|
42
42
|
current_task: Optional[Any] = None
|
|
43
43
|
todos: list[TodoItem] = field(default_factory=list)
|
|
44
|
+
# ESC key tracking for double-press functionality
|
|
45
|
+
esc_press_count: int = 0
|
|
46
|
+
last_esc_time: Optional[float] = None
|
|
47
|
+
operation_cancelled: bool = False
|
|
44
48
|
# Enhanced tracking for thoughts display
|
|
45
49
|
files_in_context: set[str] = field(default_factory=set)
|
|
46
50
|
tool_calls: list[dict[str, Any]] = field(default_factory=list)
|
|
@@ -8,6 +8,7 @@ from abc import ABC, abstractmethod
|
|
|
8
8
|
|
|
9
9
|
from pydantic_ai.exceptions import ModelRetry
|
|
10
10
|
|
|
11
|
+
from tunacode.core.logging.logger import get_logger
|
|
11
12
|
from tunacode.exceptions import FileOperationError, ToolExecutionError
|
|
12
13
|
from tunacode.types import FilePath, ToolName, ToolResult, UILogger
|
|
13
14
|
|
|
@@ -22,6 +23,7 @@ class BaseTool(ABC):
|
|
|
22
23
|
ui_logger: UI logger instance for displaying messages
|
|
23
24
|
"""
|
|
24
25
|
self.ui = ui_logger
|
|
26
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
25
27
|
|
|
26
28
|
async def execute(self, *args, **kwargs) -> ToolResult:
|
|
27
29
|
"""Execute the tool with error handling and logging.
|
|
@@ -39,14 +41,17 @@ class BaseTool(ABC):
|
|
|
39
41
|
ToolExecutionError: Raised for all other errors with structured information
|
|
40
42
|
"""
|
|
41
43
|
try:
|
|
44
|
+
msg = f"{self.tool_name}({self._format_args(*args, **kwargs)})"
|
|
42
45
|
if self.ui:
|
|
43
|
-
await self.ui.info(
|
|
46
|
+
await self.ui.info(msg)
|
|
47
|
+
self.logger.info(msg)
|
|
44
48
|
result = await self._execute(*args, **kwargs)
|
|
45
49
|
return result
|
|
46
50
|
except ModelRetry as e:
|
|
47
51
|
# Log as warning and re-raise for pydantic-ai
|
|
48
52
|
if self.ui:
|
|
49
53
|
await self.ui.warning(str(e))
|
|
54
|
+
self.logger.warning(f"ModelRetry: {e}")
|
|
50
55
|
raise
|
|
51
56
|
except ToolExecutionError:
|
|
52
57
|
# Already properly formatted, just re-raise
|
|
@@ -90,6 +95,7 @@ class BaseTool(ABC):
|
|
|
90
95
|
err_msg = f"Error {self._get_error_context(*args, **kwargs)}: {error}"
|
|
91
96
|
if self.ui:
|
|
92
97
|
await self.ui.error(err_msg)
|
|
98
|
+
self.logger.error(err_msg)
|
|
93
99
|
|
|
94
100
|
# Raise proper exception instead of returning string
|
|
95
101
|
raise ToolExecutionError(tool_name=self.tool_name, message=str(error), original_error=error)
|
|
@@ -13,7 +13,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Literal, Optional, Prot
|
|
|
13
13
|
# Try to import pydantic-ai types if available
|
|
14
14
|
try:
|
|
15
15
|
from pydantic_ai import Agent
|
|
16
|
-
from pydantic_ai.messages import ModelRequest,
|
|
16
|
+
from pydantic_ai.messages import ModelRequest, ToolReturnPart
|
|
17
17
|
|
|
18
18
|
PydanticAgent = Agent
|
|
19
19
|
MessagePart = Union[ToolReturnPart, Any]
|