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.

Files changed (207) hide show
  1. tunacode_cli-0.0.55/CLAUDE.md +78 -0
  2. {tunacode_cli-0.0.53/src/tunacode_cli.egg-info → tunacode_cli-0.0.55}/PKG-INFO +1 -1
  3. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/pyproject.toml +18 -1
  4. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/__init__.py +2 -0
  5. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/registry.py +4 -1
  6. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl.py +23 -7
  7. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/tool_executor.py +6 -4
  8. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/constants.py +1 -1
  9. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/__init__.py +20 -0
  10. tunacode_cli-0.0.55/src/tunacode/core/agents/agent_components/agent_helpers.py +219 -0
  11. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/node_processor.py +39 -74
  12. tunacode_cli-0.0.55/src/tunacode/core/agents/agent_components/truncation_checker.py +81 -0
  13. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/main.py +86 -312
  14. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/state.py +7 -3
  15. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/prompts/system.md +5 -4
  16. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep.py +12 -1
  17. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/console.py +2 -0
  18. tunacode_cli-0.0.55/src/tunacode/ui/keybindings.py +50 -0
  19. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/output.py +39 -4
  20. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/panels.py +56 -2
  21. tunacode_cli-0.0.55/src/tunacode/ui/tool_descriptions.py +115 -0
  22. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55/src/tunacode_cli.egg-info}/PKG-INFO +1 -1
  23. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/SOURCES.txt +6 -0
  24. tunacode_cli-0.0.55/tests/characterization/repl/test_escape_key_behavior.py +98 -0
  25. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_keyboard_interrupts.py +1 -1
  26. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_repl.py +2 -2
  27. tunacode_cli-0.0.55/tests/characterization/ui/test_spinner_messages.py +99 -0
  28. tunacode_cli-0.0.55/tests/test_spinner_updates.py +110 -0
  29. tunacode_cli-0.0.53/CLAUDE.md +0 -176
  30. tunacode_cli-0.0.53/src/tunacode/ui/keybindings.py +0 -82
  31. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/LICENSE +0 -0
  32. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/MANIFEST.in +0 -0
  33. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/README.md +0 -0
  34. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/setup.cfg +0 -0
  35. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/setup.py +0 -0
  36. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/__init__.py +0 -0
  37. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/__init__.py +0 -0
  38. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/base.py +0 -0
  39. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/__init__.py +0 -0
  40. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/conversation.py +0 -0
  41. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/debug.py +0 -0
  42. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/development.py +0 -0
  43. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/model.py +0 -0
  44. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/system.py +0 -0
  45. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/template.py +0 -0
  46. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/implementations/todo.py +0 -0
  47. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/commands/template_shortcut.py +0 -0
  48. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/main.py +0 -0
  49. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/__init__.py +0 -0
  50. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/command_parser.py +0 -0
  51. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/error_recovery.py +0 -0
  52. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/cli/repl_components/output_display.py +0 -0
  53. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/__init__.py +0 -0
  54. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/defaults.py +0 -0
  55. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/models.py +0 -0
  56. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/configuration/settings.py +0 -0
  57. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/context.py +0 -0
  58. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/__init__.py +0 -0
  59. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/__init__.py +0 -0
  60. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/agent_config.py +0 -0
  61. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/json_tool_parser.py +0 -0
  62. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/message_handler.py +0 -0
  63. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/response_state.py +0 -0
  64. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/result_wrapper.py +0 -0
  65. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/task_completion.py +0 -0
  66. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/tool_buffer.py +0 -0
  67. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/agent_components/tool_executor.py +0 -0
  68. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/agents/utils.py +0 -0
  69. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/background/__init__.py +0 -0
  70. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/background/manager.py +0 -0
  71. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/code_index.py +0 -0
  72. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/llm/__init__.py +0 -0
  73. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/__init__.py +0 -0
  74. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/config.py +0 -0
  75. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/formatters.py +0 -0
  76. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/handlers.py +0 -0
  77. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/logging/logger.py +0 -0
  78. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/__init__.py +0 -0
  79. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/agent_setup.py +0 -0
  80. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/base.py +0 -0
  81. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/config_setup.py +0 -0
  82. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/coordinator.py +0 -0
  83. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/environment_setup.py +0 -0
  84. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  85. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/setup/template_setup.py +0 -0
  86. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/token_usage/api_response_parser.py +0 -0
  87. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/token_usage/cost_calculator.py +0 -0
  88. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/token_usage/usage_tracker.py +0 -0
  89. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/core/tool_handler.py +0 -0
  90. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/exceptions.py +0 -0
  91. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/py.typed +0 -0
  92. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/services/__init__.py +0 -0
  93. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/services/mcp.py +0 -0
  94. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/setup.py +0 -0
  95. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/templates/__init__.py +0 -0
  96. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/templates/loader.py +0 -0
  97. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/__init__.py +0 -0
  98. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/base.py +0 -0
  99. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/bash.py +0 -0
  100. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/glob.py +0 -0
  101. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/__init__.py +0 -0
  102. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/file_filter.py +0 -0
  103. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/pattern_matcher.py +0 -0
  104. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/result_formatter.py +0 -0
  105. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/grep_components/search_result.py +0 -0
  106. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/list_dir.py +0 -0
  107. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/read_file.py +0 -0
  108. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/read_file_async_poc.py +0 -0
  109. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/run_command.py +0 -0
  110. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/todo.py +0 -0
  111. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/update_file.py +0 -0
  112. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/tools/write_file.py +0 -0
  113. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/types.py +0 -0
  114. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/__init__.py +0 -0
  115. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/completers.py +0 -0
  116. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/constants.py +0 -0
  117. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/decorators.py +0 -0
  118. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/input.py +0 -0
  119. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/lexers.py +0 -0
  120. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/logging_compat.py +0 -0
  121. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/prompt_manager.py +0 -0
  122. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/tool_ui.py +0 -0
  123. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/utils.py +0 -0
  124. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/ui/validators.py +0 -0
  125. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/__init__.py +0 -0
  126. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/bm25.py +0 -0
  127. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/diff_utils.py +0 -0
  128. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/file_utils.py +0 -0
  129. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/import_cache.py +0 -0
  130. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/message_utils.py +0 -0
  131. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/retry.py +0 -0
  132. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/ripgrep.py +0 -0
  133. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/security.py +0 -0
  134. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/system.py +0 -0
  135. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/text_utils.py +0 -0
  136. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/token_counter.py +0 -0
  137. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode/utils/user_configuration.py +0 -0
  138. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  139. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  140. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/requires.txt +0 -0
  141. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  142. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/__init__.py +0 -0
  143. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/__init__.py +0 -0
  144. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/conftest.py +0 -0
  145. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_agent_creation.py +0 -0
  146. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
  147. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_process_node.py +0 -0
  148. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_process_request.py +0 -0
  149. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/agent/test_tool_message_patching.py +0 -0
  150. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_background_edge_cases.py +0 -0
  151. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_cleanup.py +0 -0
  152. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_task_cancellation.py +0 -0
  153. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_task_creation.py +0 -0
  154. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/background/test_task_execution.py +0 -0
  155. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_cache_management.py +0 -0
  156. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_file_scanning.py +0 -0
  157. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_index_building.py +0 -0
  158. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_search_operations.py +0 -0
  159. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
  160. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/commands/__init__.py +0 -0
  161. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/commands/test_init_command.py +0 -0
  162. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/conftest.py +0 -0
  163. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/__init__.py +0 -0
  164. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_context_acceptance.py +0 -0
  165. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_context_integration.py +0 -0
  166. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_context_loading.py +0 -0
  167. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/context/test_tunacode_logging.py +0 -0
  168. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_command_parsing.py +0 -0
  169. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_error_handling.py +0 -0
  170. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_input_handling.py +0 -0
  171. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_multiline_input.py +0 -0
  172. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_output_display_logic.py +0 -0
  173. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_repl_initialization.py +0 -0
  174. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/repl/test_session_flow.py +0 -0
  175. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_error_recovery.py +0 -0
  176. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_llm_routing.py +0 -0
  177. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_mcp_integration.py +0 -0
  178. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/services/test_service_lifecycle.py +0 -0
  179. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_agent_tracking.py +0 -0
  180. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_message_history.py +0 -0
  181. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_permissions.py +0 -0
  182. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_session_management.py +0 -0
  183. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_state_initialization.py +0 -0
  184. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/state/test_user_config.py +0 -0
  185. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_commands.py +0 -0
  186. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_grep.py +0 -0
  187. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/test_characterization_main.py +0 -0
  188. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_async_ui.py +0 -0
  189. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_console_output.py +0 -0
  190. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_diff_display.py +0 -0
  191. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_prompt_rendering.py +0 -0
  192. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/ui/test_tool_confirmations.py +0 -0
  193. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_expand_file_refs.py +0 -0
  194. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_file_operations.py +0 -0
  195. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_git_commands.py +0 -0
  196. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_token_counting.py +0 -0
  197. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
  198. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/conftest.py +0 -0
  199. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_agent_output_formatting.py +0 -0
  200. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_completion_detection.py +0 -0
  201. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_json_retry.py +0 -0
  202. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_logging_config.py +0 -0
  203. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_phase2_type_hints.py +0 -0
  204. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_prompt_changes_validation.py +0 -0
  205. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_security.py +0 -0
  206. {tunacode_cli-0.0.53 → tunacode_cli-0.0.55}/tests/test_tool_batching_retry.py +0 -0
  207. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.53
3
+ Version: 0.0.55
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "tunacode-cli"
7
7
 
8
- version = "0.0.53"
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
- MSG_HIT_CTRL_C = "Hit Ctrl+C again to exit"
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
- ctrl_c_pressed = False
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
- if ctrl_c_pressed:
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
- ctrl_c_pressed = True
276
- await ui.warning(MSG_HIT_CTRL_C)
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
- ctrl_c_pressed = False
298
+ abort_pressed = False
283
299
 
284
300
  if line.lower() in ["exit", "quit"]:
285
301
  break
@@ -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
- if not state_manager.session.is_streaming_active and state_manager.session.spinner:
47
- state_manager.session.spinner.stop()
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
- if not state_manager.session.is_streaming_active and state_manager.session.spinner:
84
- state_manager.session.spinner.start()
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()
@@ -9,7 +9,7 @@ from enum import Enum
9
9
 
10
10
  # Application info
11
11
  APP_NAME = "TunaCode"
12
- APP_VERSION = "0.0.53"
12
+ APP_VERSION = "0.0.55"
13
13
 
14
14
 
15
15
  # File patterns
@@ -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 = _check_for_truncation(combined_content)
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