tunacode-cli 0.0.27__tar.gz → 0.0.29__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 (102) hide show
  1. tunacode_cli-0.0.29/PKG-INFO +121 -0
  2. tunacode_cli-0.0.29/README.md +85 -0
  3. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/pyproject.toml +1 -1
  4. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/cli/commands.py +83 -4
  5. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/configuration/defaults.py +1 -0
  6. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/constants.py +1 -1
  7. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/agents/main.py +83 -2
  8. tunacode_cli-0.0.29/src/tunacode/core/agents/orchestrator.py +213 -0
  9. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/agents/readonly.py +18 -4
  10. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/output.py +1 -1
  11. tunacode_cli-0.0.29/src/tunacode_cli.egg-info/PKG-INFO +121 -0
  12. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_fallback_responses.py +3 -1
  13. tunacode_cli-0.0.27/PKG-INFO +0 -567
  14. tunacode_cli-0.0.27/README.md +0 -531
  15. tunacode_cli-0.0.27/src/tunacode/core/agents/orchestrator.py +0 -117
  16. tunacode_cli-0.0.27/src/tunacode_cli.egg-info/PKG-INFO +0 -567
  17. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/CLAUDE.md +0 -0
  18. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/LICENSE +0 -0
  19. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/MANIFEST.in +0 -0
  20. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/TUNACODE.md +0 -0
  21. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/setup.cfg +0 -0
  22. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/setup.py +0 -0
  23. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/__init__.py +0 -0
  24. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/cli/__init__.py +0 -0
  25. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/cli/main.py +0 -0
  26. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/cli/repl.py +0 -0
  27. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/cli/textual_app.py +0 -0
  28. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/cli/textual_bridge.py +0 -0
  29. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/configuration/__init__.py +0 -0
  30. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/configuration/models.py +0 -0
  31. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/configuration/settings.py +0 -0
  32. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/context.py +0 -0
  33. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/__init__.py +0 -0
  34. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/agents/__init__.py +0 -0
  35. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/agents/planner_schema.py +0 -0
  36. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/background/__init__.py +0 -0
  37. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/background/manager.py +0 -0
  38. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/llm/__init__.py +0 -0
  39. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/llm/planner.py +0 -0
  40. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/__init__.py +0 -0
  41. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/agent_setup.py +0 -0
  42. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/base.py +0 -0
  43. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/config_setup.py +0 -0
  44. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/coordinator.py +0 -0
  45. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/environment_setup.py +0 -0
  46. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  47. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/state.py +0 -0
  48. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/core/tool_handler.py +0 -0
  49. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/exceptions.py +0 -0
  50. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/prompts/system.md +0 -0
  51. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/py.typed +0 -0
  52. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/services/__init__.py +0 -0
  53. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/services/mcp.py +0 -0
  54. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/setup.py +0 -0
  55. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/__init__.py +0 -0
  56. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/base.py +0 -0
  57. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/bash.py +0 -0
  58. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/grep.py +0 -0
  59. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/read_file.py +0 -0
  60. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/run_command.py +0 -0
  61. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/update_file.py +0 -0
  62. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/tools/write_file.py +0 -0
  63. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/types.py +0 -0
  64. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/__init__.py +0 -0
  65. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/completers.py +0 -0
  66. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/console.py +0 -0
  67. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/constants.py +0 -0
  68. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/decorators.py +0 -0
  69. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/input.py +0 -0
  70. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/keybindings.py +0 -0
  71. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/lexers.py +0 -0
  72. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/panels.py +0 -0
  73. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/prompt_manager.py +0 -0
  74. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/tool_ui.py +0 -0
  75. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/ui/validators.py +0 -0
  76. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/__init__.py +0 -0
  77. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/bm25.py +0 -0
  78. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/diff_utils.py +0 -0
  79. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/file_utils.py +0 -0
  80. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/import_cache.py +0 -0
  81. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/ripgrep.py +0 -0
  82. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/system.py +0 -0
  83. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/text_utils.py +0 -0
  84. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode/utils/user_configuration.py +0 -0
  85. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode_cli.egg-info/SOURCES.txt +0 -0
  86. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  87. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  88. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode_cli.egg-info/requires.txt +0 -0
  89. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  90. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_agent_initialization.py +0 -0
  91. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_architect_integration.py +0 -0
  92. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_architect_simple.py +0 -0
  93. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_background_manager.py +0 -0
  94. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_config_setup_async.py +0 -0
  95. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_fast_glob_search.py +0 -0
  96. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_file_reference_expansion.py +0 -0
  97. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_json_tool_parsing.py +0 -0
  98. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_orchestrator_file_references.py +0 -0
  99. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_orchestrator_import.py +0 -0
  100. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_orchestrator_planning_visibility.py +0 -0
  101. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_react_thoughts.py +0 -0
  102. {tunacode_cli-0.0.27 → tunacode_cli-0.0.29}/tests/test_update_command.py +0 -0
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: tunacode-cli
3
+ Version: 0.0.29
4
+ Summary: Your agentic CLI developer.
5
+ Author-email: larock22 <noreply@github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/larock22/tunacode
8
+ Project-URL: Repository, https://github.com/larock22/tunacode
9
+ Keywords: cli,agent,development,automation
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Topic :: Utilities
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: typer==0.15.3
23
+ Requires-Dist: prompt_toolkit==3.0.51
24
+ Requires-Dist: pydantic-ai[logfire]==0.2.6
25
+ Requires-Dist: pygments==2.19.1
26
+ Requires-Dist: rich==14.0.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build; extra == "dev"
29
+ Requires-Dist: black; extra == "dev"
30
+ Requires-Dist: flake8; extra == "dev"
31
+ Requires-Dist: isort; extra == "dev"
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: textual-dev; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # TunaCode
38
+
39
+ <div align="center">
40
+
41
+ [![PyPI version](https://badge.fury.io/py/tunacode-cli.svg)](https://badge.fury.io/py/tunacode-cli)
42
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
44
+
45
+ **AI-powered CLI coding assistant**
46
+
47
+ ![Demo](demo.gif)
48
+
49
+ </div>
50
+
51
+ ---
52
+
53
+ ## Quick Install
54
+
55
+ ```bash
56
+ # Option 1: One-line install (Linux/macOS)
57
+ wget -qO- https://raw.githubusercontent.com/alchemiststudiosDOTai/tunacode/master/scripts/install_linux.sh | bash
58
+
59
+ # Option 2: pip install
60
+ pip install tunacode-cli
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ Choose your AI provider and set your API key:
66
+
67
+ ```bash
68
+ # OpenAI
69
+ tunacode --model "openai:gpt-4o" --key "sk-your-openai-key"
70
+
71
+ # Anthropic Claude
72
+ tunacode --model "anthropic:claude-3.5-sonnet" --key "sk-ant-your-anthropic-key"
73
+
74
+ # OpenRouter (100+ models)
75
+ tunacode --model "openrouter:openai/gpt-4o" --key "sk-or-your-openrouter-key"
76
+ ```
77
+
78
+ Your config is saved to `~/.config/tunacode.json`
79
+
80
+ ## Start Coding
81
+
82
+ ```bash
83
+ tunacode
84
+ ```
85
+
86
+ ## Basic Commands
87
+
88
+ | Command | Description |
89
+ | ------- | ----------- |
90
+ | `/help` | Show all commands |
91
+ | `/model <provider:name>` | Switch model |
92
+ | `/clear` | Clear message history |
93
+ | `/compact` | Summarize conversation |
94
+ | `/branch <name>` | Create Git branch |
95
+ | `/yolo` | Skip confirmations |
96
+ | `!<command>` | Run shell command |
97
+ | `exit` | Exit TunaCode |
98
+
99
+ ## Safety First
100
+
101
+ ⚠️ **Important**: TunaCode can modify your codebase. Always:
102
+ - Use Git branches before making changes
103
+ - Review file modifications before confirming
104
+ - Keep backups of important work
105
+
106
+ ## Documentation
107
+
108
+ - [**Features**](documentation/FEATURES.md) - All features, tools, and commands
109
+ - [**Advanced Configuration**](documentation/ADVANCED-CONFIG.md) - Provider setup, MCP, customization
110
+ - [**Architecture**](documentation/ARCHITECTURE.md) - Source code organization and design
111
+ - [**Development**](documentation/DEVELOPMENT.md) - Contributing and development setup
112
+
113
+ ## Links
114
+
115
+ - [PyPI Package](https://pypi.org/project/tunacode-cli/)
116
+ - [GitHub Repository](https://github.com/alchemiststudiosDOTai/tunacode)
117
+ - [Report Issues](https://github.com/alchemiststudiosDOTai/tunacode/issues)
118
+
119
+ ---
120
+
121
+ MIT License - see [LICENSE](LICENSE) file
@@ -0,0 +1,85 @@
1
+ # TunaCode
2
+
3
+ <div align="center">
4
+
5
+ [![PyPI version](https://badge.fury.io/py/tunacode-cli.svg)](https://badge.fury.io/py/tunacode-cli)
6
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ **AI-powered CLI coding assistant**
10
+
11
+ ![Demo](demo.gif)
12
+
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## Quick Install
18
+
19
+ ```bash
20
+ # Option 1: One-line install (Linux/macOS)
21
+ wget -qO- https://raw.githubusercontent.com/alchemiststudiosDOTai/tunacode/master/scripts/install_linux.sh | bash
22
+
23
+ # Option 2: pip install
24
+ pip install tunacode-cli
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ Choose your AI provider and set your API key:
30
+
31
+ ```bash
32
+ # OpenAI
33
+ tunacode --model "openai:gpt-4o" --key "sk-your-openai-key"
34
+
35
+ # Anthropic Claude
36
+ tunacode --model "anthropic:claude-3.5-sonnet" --key "sk-ant-your-anthropic-key"
37
+
38
+ # OpenRouter (100+ models)
39
+ tunacode --model "openrouter:openai/gpt-4o" --key "sk-or-your-openrouter-key"
40
+ ```
41
+
42
+ Your config is saved to `~/.config/tunacode.json`
43
+
44
+ ## Start Coding
45
+
46
+ ```bash
47
+ tunacode
48
+ ```
49
+
50
+ ## Basic Commands
51
+
52
+ | Command | Description |
53
+ | ------- | ----------- |
54
+ | `/help` | Show all commands |
55
+ | `/model <provider:name>` | Switch model |
56
+ | `/clear` | Clear message history |
57
+ | `/compact` | Summarize conversation |
58
+ | `/branch <name>` | Create Git branch |
59
+ | `/yolo` | Skip confirmations |
60
+ | `!<command>` | Run shell command |
61
+ | `exit` | Exit TunaCode |
62
+
63
+ ## Safety First
64
+
65
+ ⚠️ **Important**: TunaCode can modify your codebase. Always:
66
+ - Use Git branches before making changes
67
+ - Review file modifications before confirming
68
+ - Keep backups of important work
69
+
70
+ ## Documentation
71
+
72
+ - [**Features**](documentation/FEATURES.md) - All features, tools, and commands
73
+ - [**Advanced Configuration**](documentation/ADVANCED-CONFIG.md) - Provider setup, MCP, customization
74
+ - [**Architecture**](documentation/ARCHITECTURE.md) - Source code organization and design
75
+ - [**Development**](documentation/DEVELOPMENT.md) - Contributing and development setup
76
+
77
+ ## Links
78
+
79
+ - [PyPI Package](https://pypi.org/project/tunacode-cli/)
80
+ - [GitHub Repository](https://github.com/alchemiststudiosDOTai/tunacode)
81
+ - [Report Issues](https://github.com/alchemiststudiosDOTai/tunacode/issues)
82
+
83
+ ---
84
+
85
+ MIT License - see [LICENSE](LICENSE) file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.27"
7
+ version = "0.0.29"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -521,13 +521,92 @@ class CompactCommand(SimpleCommand):
521
521
  await ui.error("Compact command not available - process_request not configured")
522
522
  return
523
523
 
524
- # Get the current agent, create a summary of context, and trim message history
525
- await process_request(
526
- "Summarize the conversation so far", context.state_manager, output=False
524
+ # Count current messages
525
+ original_count = len(context.state_manager.session.messages)
526
+
527
+ # Generate summary with output captured
528
+ summary_prompt = (
529
+ "Summarize the conversation so far in a concise paragraph, "
530
+ "focusing on the main topics discussed and any important context "
531
+ "that should be preserved."
527
532
  )
528
- await ui.success("Context history has been summarized and truncated.")
533
+ result = await process_request(
534
+ summary_prompt,
535
+ context.state_manager,
536
+ output=False, # We'll handle the output ourselves
537
+ )
538
+
539
+ # Extract summary text from result
540
+ summary_text = ""
541
+
542
+ # First try: standard result structure
543
+ if (
544
+ result
545
+ and hasattr(result, "result")
546
+ and result.result
547
+ and hasattr(result.result, "output")
548
+ ):
549
+ summary_text = result.result.output
550
+
551
+ # Second try: check messages for assistant response
552
+ if not summary_text:
553
+ messages = context.state_manager.session.messages
554
+ # Look through new messages in reverse order
555
+ for i in range(len(messages) - 1, original_count - 1, -1):
556
+ msg = messages[i]
557
+ # Handle ModelResponse objects
558
+ if hasattr(msg, "parts") and msg.parts:
559
+ for part in msg.parts:
560
+ if hasattr(part, "content") and part.content:
561
+ content = part.content
562
+ # Skip JSON thought objects
563
+ if content.strip().startswith('{"thought"'):
564
+ lines = content.split("\n")
565
+ # Find the actual summary after the JSON
566
+ for i, line in enumerate(lines):
567
+ if (
568
+ line.strip()
569
+ and not line.strip().startswith("{")
570
+ and not line.strip().endswith("}")
571
+ ):
572
+ summary_text = "\n".join(lines[i:]).strip()
573
+ break
574
+ else:
575
+ summary_text = content
576
+ if summary_text:
577
+ break
578
+ # Handle dict-style messages
579
+ elif isinstance(msg, dict):
580
+ if msg.get("role") == "assistant" and msg.get("content"):
581
+ summary_text = msg["content"]
582
+ break
583
+ # Handle other message types
584
+ elif hasattr(msg, "content") and hasattr(msg, "role"):
585
+ if getattr(msg, "role", None) == "assistant":
586
+ summary_text = msg.content
587
+ break
588
+
589
+ if summary_text:
590
+ break
591
+
592
+ if not summary_text:
593
+ await ui.error("Failed to generate summary - no assistant response found")
594
+ return
595
+
596
+ # Display summary in a formatted panel
597
+ from tunacode.ui import panels
598
+
599
+ await panels.panel("Conversation Summary", summary_text, border_style="cyan")
600
+
601
+ # Show statistics
602
+ await ui.info(f"Current message count: {original_count}")
603
+ await ui.info("After compaction: 3 (summary + last 2 messages)")
604
+
605
+ # Truncate the conversation history
529
606
  context.state_manager.session.messages = context.state_manager.session.messages[-2:]
530
607
 
608
+ await ui.success("Context history has been summarized and truncated.")
609
+
531
610
 
532
611
  class UpdateCommand(SimpleCommand):
533
612
  """Update TunaCode to the latest version."""
@@ -22,6 +22,7 @@ DEFAULT_USER_CONFIG: UserConfig = {
22
22
  "tool_ignore": [TOOL_READ_FILE],
23
23
  "guide_file": GUIDE_FILE_NAME,
24
24
  "fallback_response": True,
25
+ "fallback_verbosity": "normal", # Options: minimal, normal, detailed
25
26
  },
26
27
  "mcpServers": {},
27
28
  }
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
7
7
 
8
8
  # Application info
9
9
  APP_NAME = "TunaCode"
10
- APP_VERSION = "0.0.27"
10
+ APP_VERSION = "0.0.29"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -375,10 +375,91 @@ async def process_request(
375
375
  if not response_state.has_user_response and i >= max_iterations and fallback_enabled:
376
376
  patch_tool_messages("Task incomplete", state_manager=state_manager)
377
377
  response_state.has_final_synthesis = True
378
+
379
+ # Extract context from the agent run
380
+ tool_calls_summary = []
381
+ files_modified = set()
382
+ commands_run = []
383
+
384
+ # Analyze message history for context
385
+ for msg in state_manager.session.messages:
386
+ if hasattr(msg, "parts"):
387
+ for part in msg.parts:
388
+ if hasattr(part, "part_kind") and part.part_kind == "tool-call":
389
+ tool_name = getattr(part, "tool_name", "unknown")
390
+ tool_calls_summary.append(tool_name)
391
+
392
+ # Track specific operations
393
+ if tool_name in ["write_file", "update_file"] and hasattr(part, "args"):
394
+ if "file_path" in part.args:
395
+ files_modified.add(part.args["file_path"])
396
+ elif tool_name in ["run_command", "bash"] and hasattr(part, "args"):
397
+ if "command" in part.args:
398
+ commands_run.append(part.args["command"])
399
+
400
+ # Build fallback response with context
378
401
  fallback = FallbackResponse(
379
402
  summary="Reached maximum iterations without producing a final response.",
380
- progress=f"{i}/{max_iterations} iterations completed",
403
+ progress=f"Completed {i} iterations (limit: {max_iterations})",
404
+ )
405
+
406
+ # Get verbosity setting
407
+ verbosity = state_manager.session.user_config.get("settings", {}).get(
408
+ "fallback_verbosity", "normal"
409
+ )
410
+
411
+ if verbosity in ["normal", "detailed"]:
412
+ # Add what was attempted
413
+ if tool_calls_summary:
414
+ tool_counts = {}
415
+ for tool in tool_calls_summary:
416
+ tool_counts[tool] = tool_counts.get(tool, 0) + 1
417
+
418
+ fallback.issues.append(f"Executed {len(tool_calls_summary)} tool calls:")
419
+ for tool, count in sorted(tool_counts.items()):
420
+ fallback.issues.append(f" • {tool}: {count}x")
421
+
422
+ if verbosity == "detailed":
423
+ if files_modified:
424
+ fallback.issues.append(f"\nFiles modified ({len(files_modified)}):")
425
+ for f in sorted(files_modified)[:5]: # Limit to 5 files
426
+ fallback.issues.append(f" • {f}")
427
+ if len(files_modified) > 5:
428
+ fallback.issues.append(f" • ... and {len(files_modified) - 5} more")
429
+
430
+ if commands_run:
431
+ fallback.issues.append(f"\nCommands executed ({len(commands_run)}):")
432
+ for cmd in commands_run[:3]: # Limit to 3 commands
433
+ # Truncate long commands
434
+ display_cmd = cmd if len(cmd) <= 60 else cmd[:57] + "..."
435
+ fallback.issues.append(f" • {display_cmd}")
436
+ if len(commands_run) > 3:
437
+ fallback.issues.append(f" • ... and {len(commands_run) - 3} more")
438
+
439
+ # Add helpful next steps
440
+ fallback.next_steps.append(
441
+ "The task may be too complex - try breaking it into smaller steps"
381
442
  )
443
+ fallback.next_steps.append("Check the output above for any errors or partial progress")
444
+ if files_modified:
445
+ fallback.next_steps.append("Review modified files to see what changes were made")
446
+
447
+ # Create comprehensive output
448
+ output_parts = [fallback.summary, ""]
449
+
450
+ if fallback.progress:
451
+ output_parts.append(f"Progress: {fallback.progress}")
452
+
453
+ if fallback.issues:
454
+ output_parts.append("\nWhat happened:")
455
+ output_parts.extend(fallback.issues)
456
+
457
+ if fallback.next_steps:
458
+ output_parts.append("\nSuggested next steps:")
459
+ for step in fallback.next_steps:
460
+ output_parts.append(f" • {step}")
461
+
462
+ comprehensive_output = "\n".join(output_parts)
382
463
 
383
464
  # Create a wrapper object that mimics AgentRun with the required attributes
384
465
  class AgentRunWrapper:
@@ -391,7 +472,7 @@ async def process_request(
391
472
  # Delegate all other attributes to the wrapped object
392
473
  return getattr(self._wrapped, name)
393
474
 
394
- return AgentRunWrapper(agent_run, SimpleResult(fallback.summary))
475
+ return AgentRunWrapper(agent_run, SimpleResult(comprehensive_output))
395
476
 
396
477
  # For non-fallback cases, we still need to handle the response_state
397
478
  # Create a minimal wrapper just to add response_state
@@ -0,0 +1,213 @@
1
+ """Agent orchestration scaffolding.
2
+
3
+ This module defines an ``OrchestratorAgent`` class that demonstrates how
4
+ higher level planning and delegation could be layered on top of the existing
5
+ ``process_request`` workflow. The goal is to keep orchestration logic isolated
6
+ from the core agent implementation while reusing all current tooling and state
7
+ handling provided by ``main.process_request``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import itertools
14
+ from typing import List
15
+
16
+ from ...types import AgentRun, FallbackResponse, ModelName, ResponseState
17
+ from ..llm.planner import make_plan
18
+ from ..state import StateManager
19
+ from . import main as agent_main
20
+ from .planner_schema import Task
21
+ from .readonly import ReadOnlyAgent
22
+
23
+
24
+ class OrchestratorAgent:
25
+ """Plan and run a sequence of sub-agent tasks."""
26
+
27
+ def __init__(self, state_manager: StateManager):
28
+ self.state = state_manager
29
+
30
+ async def plan(self, request: str, model: ModelName) -> List[Task]:
31
+ """Plan tasks for a user request using the planner LLM."""
32
+
33
+ return await make_plan(request, model, self.state)
34
+
35
+ async def _run_sub_task(self, task: Task, model: ModelName) -> AgentRun:
36
+ """Execute a single task using an appropriate sub-agent."""
37
+ from rich.console import Console
38
+
39
+ console = Console()
40
+
41
+ # Show which task is being executed
42
+ task_type = "WRITE" if task.mutate else "READ"
43
+ console.print(f"\n[dim][Task {task.id}] {task_type}[/dim]")
44
+ console.print(f"[dim] → {task.description}[/dim]")
45
+
46
+ if task.mutate:
47
+ agent_main.get_or_create_agent(model, self.state)
48
+ result = await agent_main.process_request(model, task.description, self.state)
49
+ else:
50
+ agent = ReadOnlyAgent(model, self.state)
51
+ result = await agent.process_request(task.description)
52
+
53
+ console.print(f"[dim][Task {task.id}] Complete[/dim]")
54
+ return result
55
+
56
+ async def run(self, request: str, model: ModelName | None = None) -> List[AgentRun]:
57
+ """Plan and execute a user request.
58
+
59
+ Parameters
60
+ ----------
61
+ request:
62
+ The high level user request to process.
63
+ model:
64
+ Optional model name to use for sub agents. Defaults to the current
65
+ session model.
66
+ """
67
+ from rich.console import Console
68
+
69
+ console = Console()
70
+ model = model or self.state.session.current_model
71
+
72
+ # Track response state across all sub-tasks
73
+ response_state = ResponseState()
74
+
75
+ # Show orchestrator is starting
76
+ console.print(
77
+ "\n[cyan]Orchestrator Mode: Analyzing request and creating execution plan...[/cyan]"
78
+ )
79
+
80
+ tasks = await self.plan(request, model)
81
+
82
+ # Show execution is starting
83
+ console.print(f"\n[cyan]Executing plan with {len(tasks)} tasks...[/cyan]")
84
+
85
+ results: List[AgentRun] = []
86
+ task_progress = []
87
+
88
+ for mutate_flag, group in itertools.groupby(tasks, key=lambda t: t.mutate):
89
+ if mutate_flag:
90
+ for t in group:
91
+ result = await self._run_sub_task(t, model)
92
+ results.append(result)
93
+
94
+ # Track task progress
95
+ task_progress.append(
96
+ {
97
+ "task": t,
98
+ "completed": True,
99
+ "had_output": hasattr(result, "result")
100
+ and result.result
101
+ and getattr(result.result, "output", None),
102
+ }
103
+ )
104
+
105
+ # Check if this task produced user-visible output
106
+ if hasattr(result, "response_state"):
107
+ response_state.has_user_response |= result.response_state.has_user_response
108
+ else:
109
+ # Show parallel execution
110
+ task_list = list(group)
111
+ if len(task_list) > 1:
112
+ console.print(
113
+ f"\n[dim][Parallel Execution] Running {len(task_list)} read-only tasks concurrently...[/dim]"
114
+ )
115
+ coros = [self._run_sub_task(t, model) for t in task_list]
116
+ parallel_results = await asyncio.gather(*coros)
117
+ results.extend(parallel_results)
118
+
119
+ # Track parallel task progress
120
+ for t, result in zip(task_list, parallel_results):
121
+ task_progress.append(
122
+ {
123
+ "task": t,
124
+ "completed": True,
125
+ "had_output": hasattr(result, "result")
126
+ and result.result
127
+ and getattr(result.result, "output", None),
128
+ }
129
+ )
130
+
131
+ # Check if this task produced user-visible output
132
+ if hasattr(result, "response_state"):
133
+ response_state.has_user_response |= result.response_state.has_user_response
134
+
135
+ console.print("\n[green]Orchestrator completed all tasks successfully![/green]")
136
+
137
+ # Check if we need a fallback response
138
+ has_any_output = any(
139
+ hasattr(r, "result") and r.result and getattr(r.result, "output", None) for r in results
140
+ )
141
+
142
+ fallback_enabled = self.state.session.user_config.get("settings", {}).get(
143
+ "fallback_response", True
144
+ )
145
+
146
+ # Use has_any_output as the primary check since response_state might not be set for all agents
147
+ if not has_any_output and fallback_enabled:
148
+ # Generate a detailed fallback response
149
+ completed_count = sum(1 for tp in task_progress if tp["completed"])
150
+ output_count = sum(1 for tp in task_progress if tp["had_output"])
151
+
152
+ fallback = FallbackResponse(
153
+ summary="Orchestrator completed all tasks but no final response was generated.",
154
+ progress=f"Executed {completed_count}/{len(tasks)} tasks successfully",
155
+ )
156
+
157
+ # Add task details based on verbosity
158
+ verbosity = self.state.session.user_config.get("settings", {}).get(
159
+ "fallback_verbosity", "normal"
160
+ )
161
+
162
+ if verbosity in ["normal", "detailed"]:
163
+ # List what was done
164
+ if task_progress:
165
+ fallback.issues.append(f"Tasks executed: {completed_count}")
166
+ if output_count == 0:
167
+ fallback.issues.append("No tasks produced visible output")
168
+
169
+ if verbosity == "detailed":
170
+ # Add task descriptions
171
+ for i, tp in enumerate(task_progress, 1):
172
+ task_type = "WRITE" if tp["task"].mutate else "READ"
173
+ status = "✓" if tp["completed"] else "✗"
174
+ fallback.issues.append(
175
+ f"{status} Task {i} [{task_type}]: {tp['task'].description}"
176
+ )
177
+
178
+ # Suggest next steps
179
+ fallback.next_steps.append("Review the task execution above for any errors")
180
+ fallback.next_steps.append(
181
+ "Try running individual tasks separately for more detailed output"
182
+ )
183
+
184
+ # Create synthesized response
185
+ synthesis_parts = [fallback.summary, ""]
186
+
187
+ if fallback.progress:
188
+ synthesis_parts.append(f"Progress: {fallback.progress}")
189
+
190
+ if fallback.issues:
191
+ synthesis_parts.append("\nDetails:")
192
+ synthesis_parts.extend(f" • {issue}" for issue in fallback.issues)
193
+
194
+ if fallback.next_steps:
195
+ synthesis_parts.append("\nNext steps:")
196
+ synthesis_parts.extend(f" • {step}" for step in fallback.next_steps)
197
+
198
+ synthesis = "\n".join(synthesis_parts)
199
+
200
+ class FallbackResult:
201
+ def __init__(self, output: str, response_state: ResponseState):
202
+ self.output = output
203
+ self.response_state = response_state
204
+
205
+ class FallbackRun:
206
+ def __init__(self, synthesis: str, response_state: ResponseState):
207
+ self.result = FallbackResult(synthesis, response_state)
208
+ self.response_state = response_state
209
+
210
+ response_state.has_final_synthesis = True
211
+ results.append(FallbackRun(synthesis, response_state))
212
+
213
+ return results
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  from ...tools.grep import grep
8
8
  from ...tools.read_file import read_file
9
- from ...types import AgentRun, ModelName
9
+ from ...types import AgentRun, ModelName, ResponseState
10
10
  from ..state import StateManager
11
11
 
12
12
  if TYPE_CHECKING:
@@ -42,10 +42,24 @@ class ReadOnlyAgent:
42
42
  async def process_request(self, request: str) -> AgentRun:
43
43
  """Process a request using only read-only tools."""
44
44
  agent = self._get_agent()
45
+ response_state = ResponseState()
45
46
 
46
47
  # Use iter() like main.py does to get the full run context
47
48
  async with agent.iter(request) as agent_run:
48
- async for _ in agent_run:
49
- pass # Let it complete
49
+ async for node in agent_run:
50
+ # Check if this node produced user-visible output
51
+ if hasattr(node, "result") and node.result and hasattr(node.result, "output"):
52
+ if node.result.output:
53
+ response_state.has_user_response = True
50
54
 
51
- return agent_run
55
+ # Wrap the agent run to include response_state
56
+ class AgentRunWithState:
57
+ def __init__(self, wrapped_run):
58
+ self._wrapped = wrapped_run
59
+ self.response_state = response_state
60
+
61
+ def __getattr__(self, name):
62
+ # Delegate all other attributes to the wrapped object
63
+ return getattr(self._wrapped, name)
64
+
65
+ return AgentRunWithState(agent_run)