tunacode-cli 0.0.70__py3-none-any.whl → 0.0.78.6__py3-none-any.whl
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/commands/__init__.py +0 -2
- tunacode/cli/commands/implementations/__init__.py +0 -3
- tunacode/cli/commands/implementations/debug.py +2 -2
- tunacode/cli/commands/implementations/development.py +10 -8
- tunacode/cli/commands/implementations/model.py +357 -29
- tunacode/cli/commands/implementations/system.py +3 -2
- tunacode/cli/commands/implementations/template.py +0 -2
- tunacode/cli/commands/registry.py +8 -7
- tunacode/cli/commands/slash/loader.py +2 -1
- tunacode/cli/commands/slash/validator.py +2 -1
- tunacode/cli/main.py +19 -1
- tunacode/cli/repl.py +90 -229
- tunacode/cli/repl_components/command_parser.py +2 -1
- tunacode/cli/repl_components/error_recovery.py +8 -5
- tunacode/cli/repl_components/output_display.py +1 -10
- tunacode/cli/repl_components/tool_executor.py +1 -13
- tunacode/configuration/defaults.py +2 -2
- tunacode/configuration/key_descriptions.py +284 -0
- tunacode/configuration/settings.py +0 -1
- tunacode/constants.py +6 -42
- tunacode/core/agents/__init__.py +43 -2
- tunacode/core/agents/agent_components/__init__.py +7 -0
- tunacode/core/agents/agent_components/agent_config.py +162 -158
- tunacode/core/agents/agent_components/agent_helpers.py +31 -2
- tunacode/core/agents/agent_components/node_processor.py +180 -146
- tunacode/core/agents/agent_components/response_state.py +123 -6
- tunacode/core/agents/agent_components/state_transition.py +116 -0
- tunacode/core/agents/agent_components/streaming.py +296 -0
- tunacode/core/agents/agent_components/task_completion.py +19 -6
- tunacode/core/agents/agent_components/tool_buffer.py +21 -1
- tunacode/core/agents/agent_components/tool_executor.py +10 -0
- tunacode/core/agents/main.py +522 -370
- tunacode/core/agents/main_legact.py +538 -0
- tunacode/core/agents/prompts.py +66 -0
- tunacode/core/agents/utils.py +29 -122
- tunacode/core/setup/__init__.py +0 -2
- tunacode/core/setup/config_setup.py +88 -227
- tunacode/core/setup/config_wizard.py +230 -0
- tunacode/core/setup/coordinator.py +2 -1
- tunacode/core/state.py +16 -64
- tunacode/core/token_usage/usage_tracker.py +3 -1
- tunacode/core/tool_authorization.py +352 -0
- tunacode/core/tool_handler.py +67 -60
- tunacode/prompts/system.xml +751 -0
- tunacode/services/mcp.py +97 -1
- tunacode/setup.py +0 -23
- tunacode/tools/base.py +54 -1
- tunacode/tools/bash.py +14 -0
- tunacode/tools/glob.py +4 -2
- tunacode/tools/grep.py +7 -17
- tunacode/tools/prompts/glob_prompt.xml +1 -1
- tunacode/tools/prompts/grep_prompt.xml +1 -0
- tunacode/tools/prompts/list_dir_prompt.xml +1 -1
- tunacode/tools/prompts/react_prompt.xml +23 -0
- tunacode/tools/prompts/read_file_prompt.xml +1 -1
- tunacode/tools/react.py +153 -0
- tunacode/tools/run_command.py +15 -0
- tunacode/types.py +14 -79
- tunacode/ui/completers.py +434 -50
- tunacode/ui/config_dashboard.py +585 -0
- tunacode/ui/console.py +63 -11
- tunacode/ui/input.py +8 -3
- tunacode/ui/keybindings.py +0 -18
- tunacode/ui/model_selector.py +395 -0
- tunacode/ui/output.py +40 -19
- tunacode/ui/panels.py +173 -49
- tunacode/ui/path_heuristics.py +91 -0
- tunacode/ui/prompt_manager.py +1 -20
- tunacode/ui/tool_ui.py +30 -8
- tunacode/utils/api_key_validation.py +93 -0
- tunacode/utils/config_comparator.py +340 -0
- tunacode/utils/models_registry.py +593 -0
- tunacode/utils/text_utils.py +18 -1
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
- tunacode/cli/commands/implementations/plan.py +0 -50
- tunacode/cli/commands/implementations/todo.py +0 -217
- tunacode/context.py +0 -71
- tunacode/core/setup/git_safety_setup.py +0 -186
- tunacode/prompts/system.md +0 -359
- tunacode/prompts/system.md.bak +0 -487
- tunacode/tools/exit_plan_mode.py +0 -273
- tunacode/tools/present_plan.py +0 -288
- tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
- tunacode/tools/prompts/present_plan_prompt.xml +0 -20
- tunacode/tools/prompts/todo_prompt.xml +0 -96
- tunacode/tools/todo.py +0 -456
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Module: tunacode.core.setup.config_wizard
|
|
2
|
+
|
|
3
|
+
Wizard-style onboarding helpers extracted from ConfigSetup to reduce file size
|
|
4
|
+
and keep responsibilities focused.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Optional
|
|
10
|
+
|
|
11
|
+
from tunacode.constants import UI_COLORS
|
|
12
|
+
from tunacode.exceptions import ConfigurationError
|
|
13
|
+
from tunacode.ui import console as ui
|
|
14
|
+
from tunacode.utils import user_configuration
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfigWizard:
|
|
18
|
+
"""Encapsulates the interactive configuration wizard flow."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, state_manager, model_registry, config_file: Path):
|
|
21
|
+
self.state_manager = state_manager
|
|
22
|
+
self.model_registry = model_registry
|
|
23
|
+
self.config_file = config_file
|
|
24
|
+
self._wizard_selected_provider: Optional[Dict[str, str]] = None
|
|
25
|
+
|
|
26
|
+
async def run_onboarding(self) -> None:
|
|
27
|
+
"""Run enhanced wizard-style onboarding process for new users."""
|
|
28
|
+
initial_config = json.dumps(self.state_manager.session.user_config, sort_keys=True)
|
|
29
|
+
|
|
30
|
+
# Welcome message with provider guidance
|
|
31
|
+
await ui.panel(
|
|
32
|
+
"Welcome to TunaCode Setup Wizard!",
|
|
33
|
+
"This guided setup will help you configure TunaCode in under 5 minutes.\n"
|
|
34
|
+
"We'll help you choose a provider, set up your API keys, and configure your "
|
|
35
|
+
"preferred model.",
|
|
36
|
+
border_style=UI_COLORS["primary"],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Steps
|
|
40
|
+
await self._step1_provider_selection()
|
|
41
|
+
await self._step2_api_key_setup()
|
|
42
|
+
await self._step3_model_selection()
|
|
43
|
+
await self._step4_optional_settings()
|
|
44
|
+
|
|
45
|
+
# Save configuration and finish
|
|
46
|
+
current_config = json.dumps(self.state_manager.session.user_config, sort_keys=True)
|
|
47
|
+
if initial_config != current_config:
|
|
48
|
+
try:
|
|
49
|
+
user_configuration.save_config(self.state_manager)
|
|
50
|
+
await ui.panel(
|
|
51
|
+
"Setup Complete!",
|
|
52
|
+
f"Configuration saved to: [bold]{self.config_file}[/bold]\n\n"
|
|
53
|
+
"You're ready to start using TunaCode!\n"
|
|
54
|
+
"Use [green]/quickstart[/green] anytime for a tutorial.",
|
|
55
|
+
border_style=UI_COLORS["success"],
|
|
56
|
+
)
|
|
57
|
+
except ConfigurationError as e:
|
|
58
|
+
await ui.error(str(e))
|
|
59
|
+
|
|
60
|
+
async def _step1_provider_selection(self) -> None:
|
|
61
|
+
"""Wizard step 1: Provider selection with detailed explanations."""
|
|
62
|
+
provider_info = {
|
|
63
|
+
"1": {
|
|
64
|
+
"name": "OpenRouter",
|
|
65
|
+
"description": "Access to multiple models (GPT-4, Claude, Gemini, etc.)",
|
|
66
|
+
"signup": "https://openrouter.ai/",
|
|
67
|
+
"key_name": "OPENROUTER_API_KEY",
|
|
68
|
+
},
|
|
69
|
+
"2": {
|
|
70
|
+
"name": "OpenAI",
|
|
71
|
+
"description": "GPT-4 models",
|
|
72
|
+
"signup": "https://platform.openai.com/signup",
|
|
73
|
+
"key_name": "OPENAI_API_KEY",
|
|
74
|
+
},
|
|
75
|
+
"3": {
|
|
76
|
+
"name": "Anthropic",
|
|
77
|
+
"description": "Claude-3 models",
|
|
78
|
+
"signup": "https://console.anthropic.com/",
|
|
79
|
+
"key_name": "ANTHROPIC_API_KEY",
|
|
80
|
+
},
|
|
81
|
+
"4": {
|
|
82
|
+
"name": "Google",
|
|
83
|
+
"description": "Gemini models",
|
|
84
|
+
"signup": "https://ai.google.dev/",
|
|
85
|
+
"key_name": "GEMINI_API_KEY",
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
message = "Choose your AI provider:\n\n"
|
|
90
|
+
for key, info in provider_info.items():
|
|
91
|
+
message += f" {key} - {info['name']}: {info['description']}\n"
|
|
92
|
+
|
|
93
|
+
await ui.panel("Provider Selection", message, border_style=UI_COLORS["primary"])
|
|
94
|
+
|
|
95
|
+
while True:
|
|
96
|
+
choice = await ui.input(
|
|
97
|
+
"wizard_provider",
|
|
98
|
+
pretext=" Choose provider (1-4): ",
|
|
99
|
+
state_manager=self.state_manager,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if choice.strip() in provider_info:
|
|
103
|
+
selected = provider_info[choice.strip()]
|
|
104
|
+
self._wizard_selected_provider = selected
|
|
105
|
+
|
|
106
|
+
await ui.success(f"Selected: {selected['name']}")
|
|
107
|
+
await ui.info(f"Sign up at: {selected['signup']}")
|
|
108
|
+
break
|
|
109
|
+
else:
|
|
110
|
+
await ui.error("Please enter 1, 2, 3, or 4")
|
|
111
|
+
|
|
112
|
+
async def _step2_api_key_setup(self) -> None:
|
|
113
|
+
"""Wizard step 2: API key setup with provider-specific guidance."""
|
|
114
|
+
provider = self._wizard_selected_provider
|
|
115
|
+
|
|
116
|
+
message = f"Enter your {provider['name']} API key:\n\n"
|
|
117
|
+
message += f"Get your key from: {provider['signup']}\n"
|
|
118
|
+
message += "Your key will be stored securely in your local config"
|
|
119
|
+
|
|
120
|
+
await ui.panel(f"{provider['name']} API Key", message, border_style=UI_COLORS["primary"])
|
|
121
|
+
|
|
122
|
+
while True:
|
|
123
|
+
api_key = await ui.input(
|
|
124
|
+
"wizard_api_key",
|
|
125
|
+
pretext=f" {provider['name']} API Key: ",
|
|
126
|
+
is_password=True,
|
|
127
|
+
state_manager=self.state_manager,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if api_key.strip():
|
|
131
|
+
# Ensure env dict exists
|
|
132
|
+
if "env" not in self.state_manager.session.user_config:
|
|
133
|
+
self.state_manager.session.user_config["env"] = {}
|
|
134
|
+
|
|
135
|
+
self.state_manager.session.user_config["env"][provider["key_name"]] = (
|
|
136
|
+
api_key.strip()
|
|
137
|
+
)
|
|
138
|
+
await ui.success("API key saved successfully!")
|
|
139
|
+
break
|
|
140
|
+
else:
|
|
141
|
+
await ui.error("API key cannot be empty")
|
|
142
|
+
|
|
143
|
+
async def _step3_model_selection(self) -> None:
|
|
144
|
+
"""Wizard step 3: Model selection with smart recommendations."""
|
|
145
|
+
provider = self._wizard_selected_provider
|
|
146
|
+
|
|
147
|
+
# Provide smart recommendations based on provider
|
|
148
|
+
recommendations = {
|
|
149
|
+
"OpenAI": [
|
|
150
|
+
("openai:gpt-4o", "GPT-4o flagship multimodal model (recommended)"),
|
|
151
|
+
("openai:gpt-4.1", "Latest GPT-4.1 with enhanced coding"),
|
|
152
|
+
("openai:o3", "Advanced reasoning model for complex tasks"),
|
|
153
|
+
],
|
|
154
|
+
"Anthropic": [
|
|
155
|
+
("anthropic:claude-sonnet-4", "Claude Sonnet 4 latest generation (recommended)"),
|
|
156
|
+
("anthropic:claude-opus-4.1", "Most capable Claude with extended thinking"),
|
|
157
|
+
("anthropic:claude-3.5-sonnet", "Claude 3.5 Sonnet proven performance"),
|
|
158
|
+
],
|
|
159
|
+
"OpenRouter": [
|
|
160
|
+
(
|
|
161
|
+
"openrouter:anthropic/claude-sonnet-4",
|
|
162
|
+
"Claude Sonnet 4 via OpenRouter (recommended)",
|
|
163
|
+
),
|
|
164
|
+
("openrouter:openai/gpt-4.1", "GPT-4.1 via OpenRouter"),
|
|
165
|
+
("openrouter:google/gemini-2.5-flash", "Google Gemini 2.5 Flash latest"),
|
|
166
|
+
],
|
|
167
|
+
"Google": [
|
|
168
|
+
(
|
|
169
|
+
"google:gemini-2.5-pro",
|
|
170
|
+
"Gemini 2.5 Pro with thinking capabilities (recommended)",
|
|
171
|
+
),
|
|
172
|
+
("google:gemini-2.5-flash", "Gemini 2.5 Flash best price-performance"),
|
|
173
|
+
("google:gemini-2.0-flash", "Gemini 2.0 Flash with native tool use"),
|
|
174
|
+
],
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
models = recommendations.get(provider["name"], [])
|
|
178
|
+
message = f"Choose your default {provider['name']} model:\n\n"
|
|
179
|
+
|
|
180
|
+
for i, (model_id, description) in enumerate(models, 1):
|
|
181
|
+
message += f" {i} - {description}\n"
|
|
182
|
+
|
|
183
|
+
message += "\nYou can change this later with [green]/model[/green]"
|
|
184
|
+
|
|
185
|
+
await ui.panel("Model Selection", message, border_style=UI_COLORS["primary"])
|
|
186
|
+
|
|
187
|
+
while True:
|
|
188
|
+
choice = await ui.input(
|
|
189
|
+
"wizard_model",
|
|
190
|
+
pretext=f" Choose model (1-{len(models)}): ",
|
|
191
|
+
state_manager=self.state_manager,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
index = int(choice.strip()) - 1
|
|
196
|
+
if 0 <= index < len(models):
|
|
197
|
+
selected_model = models[index][0]
|
|
198
|
+
self.state_manager.session.user_config["default_model"] = selected_model
|
|
199
|
+
await ui.success(f"Selected: {selected_model}")
|
|
200
|
+
break
|
|
201
|
+
else:
|
|
202
|
+
await ui.error(f"Please enter a number between 1 and {len(models)}")
|
|
203
|
+
except ValueError:
|
|
204
|
+
await ui.error("Please enter a valid number")
|
|
205
|
+
|
|
206
|
+
async def _step4_optional_settings(self) -> None:
|
|
207
|
+
"""Wizard step 4: Optional settings configuration."""
|
|
208
|
+
message = "Configure optional settings:\n\n"
|
|
209
|
+
message += "• Tutorial: Enable interactive tutorial for new users\n"
|
|
210
|
+
message += "\nSkip this step to use recommended defaults"
|
|
211
|
+
|
|
212
|
+
await ui.panel("Optional Settings", message, border_style=UI_COLORS["primary"])
|
|
213
|
+
|
|
214
|
+
# Ask about tutorial
|
|
215
|
+
tutorial_choice = await ui.input(
|
|
216
|
+
"wizard_tutorial",
|
|
217
|
+
pretext=" Enable tutorial for new users? [Y/n]: ",
|
|
218
|
+
state_manager=self.state_manager,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
enable_tutorial = tutorial_choice.strip().lower() not in ["n", "no", "false"]
|
|
222
|
+
|
|
223
|
+
if "settings" not in self.state_manager.session.user_config:
|
|
224
|
+
self.state_manager.session.user_config["settings"] = {}
|
|
225
|
+
|
|
226
|
+
self.state_manager.session.user_config["settings"]["enable_tutorial"] = enable_tutorial
|
|
227
|
+
|
|
228
|
+
# Streaming is always enabled - no user choice needed
|
|
229
|
+
|
|
230
|
+
await ui.info("Optional settings configured!")
|
|
@@ -37,7 +37,8 @@ class SetupCoordinator:
|
|
|
37
37
|
raise
|
|
38
38
|
|
|
39
39
|
try:
|
|
40
|
-
# Run steps sequentially to respect dependencies
|
|
40
|
+
# Run steps sequentially to respect dependencies
|
|
41
|
+
# (ConfigSetup must complete before EnvironmentSetup)
|
|
41
42
|
for step in steps_to_run:
|
|
42
43
|
# Check if the step's execute method supports wizard_mode
|
|
43
44
|
import inspect
|
tunacode/core/state.py
CHANGED
|
@@ -10,15 +10,13 @@ import uuid
|
|
|
10
10
|
from dataclasses import dataclass, field
|
|
11
11
|
from typing import TYPE_CHECKING, Any, Optional
|
|
12
12
|
|
|
13
|
+
from tunacode.configuration.defaults import DEFAULT_USER_CONFIG
|
|
13
14
|
from tunacode.types import (
|
|
14
15
|
DeviceId,
|
|
15
16
|
InputSessions,
|
|
16
17
|
MessageHistory,
|
|
17
18
|
ModelName,
|
|
18
|
-
PlanDoc,
|
|
19
|
-
PlanPhase,
|
|
20
19
|
SessionId,
|
|
21
|
-
TodoItem,
|
|
22
20
|
ToolName,
|
|
23
21
|
UserConfig,
|
|
24
22
|
)
|
|
@@ -39,7 +37,8 @@ class SessionState:
|
|
|
39
37
|
) # Keep as dict[str, Any] for agent instances
|
|
40
38
|
messages: MessageHistory = field(default_factory=list)
|
|
41
39
|
total_cost: float = 0.0
|
|
42
|
-
|
|
40
|
+
# Keep session default in sync with configuration default
|
|
41
|
+
current_model: ModelName = DEFAULT_USER_CONFIG["default_model"]
|
|
43
42
|
spinner: Optional[Any] = None
|
|
44
43
|
tool_ignore: list[ToolName] = field(default_factory=list)
|
|
45
44
|
yolo: bool = False
|
|
@@ -49,7 +48,10 @@ class SessionState:
|
|
|
49
48
|
device_id: Optional[DeviceId] = None
|
|
50
49
|
input_sessions: InputSessions = field(default_factory=dict)
|
|
51
50
|
current_task: Optional[Any] = None
|
|
52
|
-
|
|
51
|
+
# CLAUDE_ANCHOR[react-scratchpad]: Session scratchpad for ReAct tooling
|
|
52
|
+
react_scratchpad: dict[str, Any] = field(default_factory=lambda: {"timeline": []})
|
|
53
|
+
react_forced_calls: int = 0
|
|
54
|
+
react_guidance: list[str] = field(default_factory=list)
|
|
53
55
|
# Operation state tracking
|
|
54
56
|
operation_cancelled: bool = False
|
|
55
57
|
# Enhanced tracking for thoughts display
|
|
@@ -87,12 +89,6 @@ class SessionState:
|
|
|
87
89
|
iteration_budgets: dict[str, int] = field(default_factory=dict)
|
|
88
90
|
recursive_context_stack: list[dict[str, Any]] = field(default_factory=list)
|
|
89
91
|
|
|
90
|
-
# Plan Mode state tracking
|
|
91
|
-
plan_mode: bool = False
|
|
92
|
-
plan_phase: Optional[PlanPhase] = None
|
|
93
|
-
current_plan: Optional[PlanDoc] = None
|
|
94
|
-
plan_approved: bool = False
|
|
95
|
-
|
|
96
92
|
def update_token_count(self):
|
|
97
93
|
"""Calculates the total token count from messages and files in context."""
|
|
98
94
|
message_contents = [get_message_content(msg) for msg in self.messages]
|
|
@@ -119,19 +115,6 @@ class StateManager:
|
|
|
119
115
|
def set_tool_handler(self, handler: "ToolHandler") -> None:
|
|
120
116
|
self._tool_handler = handler
|
|
121
117
|
|
|
122
|
-
def add_todo(self, todo: TodoItem) -> None:
|
|
123
|
-
self._session.todos.append(todo)
|
|
124
|
-
|
|
125
|
-
def update_todo(self, todo_id: str, status: str) -> None:
|
|
126
|
-
from datetime import datetime
|
|
127
|
-
|
|
128
|
-
for todo in self._session.todos:
|
|
129
|
-
if todo.id == todo_id:
|
|
130
|
-
todo.status = status
|
|
131
|
-
if status == "completed" and not todo.completed_at:
|
|
132
|
-
todo.completed_at = datetime.now()
|
|
133
|
-
break
|
|
134
|
-
|
|
135
118
|
def push_recursive_context(self, context: dict[str, Any]) -> None:
|
|
136
119
|
"""Push a new context onto the recursive execution stack."""
|
|
137
120
|
self._session.recursive_context_stack.append(context)
|
|
@@ -166,48 +149,17 @@ class StateManager:
|
|
|
166
149
|
self._session.iteration_budgets.clear()
|
|
167
150
|
self._session.recursive_context_stack.clear()
|
|
168
151
|
|
|
169
|
-
|
|
170
|
-
|
|
152
|
+
# React scratchpad helpers
|
|
153
|
+
def get_react_scratchpad(self) -> dict[str, Any]:
|
|
154
|
+
return self._session.react_scratchpad
|
|
155
|
+
|
|
156
|
+
def append_react_entry(self, entry: dict[str, Any]) -> None:
|
|
157
|
+
timeline = self._session.react_scratchpad.setdefault("timeline", [])
|
|
158
|
+
timeline.append(entry)
|
|
171
159
|
|
|
172
|
-
def
|
|
173
|
-
self._session.
|
|
160
|
+
def clear_react_scratchpad(self) -> None:
|
|
161
|
+
self._session.react_scratchpad = {"timeline": []}
|
|
174
162
|
|
|
175
163
|
def reset_session(self) -> None:
|
|
176
164
|
"""Reset the session to a fresh state."""
|
|
177
165
|
self._session = SessionState()
|
|
178
|
-
|
|
179
|
-
# Plan Mode methods
|
|
180
|
-
def enter_plan_mode(self) -> None:
|
|
181
|
-
"""Enter plan mode - restricts to read-only operations."""
|
|
182
|
-
self._session.plan_mode = True
|
|
183
|
-
self._session.plan_phase = PlanPhase.PLANNING_RESEARCH
|
|
184
|
-
self._session.current_plan = None
|
|
185
|
-
self._session.plan_approved = False
|
|
186
|
-
# Clear agent cache to force recreation with plan mode tools
|
|
187
|
-
self._session.agents.clear()
|
|
188
|
-
|
|
189
|
-
def exit_plan_mode(self, plan: Optional[PlanDoc] = None) -> None:
|
|
190
|
-
"""Exit plan mode with optional plan data."""
|
|
191
|
-
self._session.plan_mode = False
|
|
192
|
-
self._session.plan_phase = None
|
|
193
|
-
self._session.current_plan = plan
|
|
194
|
-
self._session.plan_approved = False
|
|
195
|
-
# Clear agent cache to force recreation without plan mode tools
|
|
196
|
-
self._session.agents.clear()
|
|
197
|
-
|
|
198
|
-
def approve_plan(self) -> None:
|
|
199
|
-
"""Mark current plan as approved for execution."""
|
|
200
|
-
self._session.plan_approved = True
|
|
201
|
-
self._session.plan_mode = False
|
|
202
|
-
|
|
203
|
-
def is_plan_mode(self) -> bool:
|
|
204
|
-
"""Check if currently in plan mode."""
|
|
205
|
-
return self._session.plan_mode
|
|
206
|
-
|
|
207
|
-
def set_current_plan(self, plan: PlanDoc) -> None:
|
|
208
|
-
"""Set the current plan data."""
|
|
209
|
-
self._session.current_plan = plan
|
|
210
|
-
|
|
211
|
-
def get_current_plan(self) -> Optional[PlanDoc]:
|
|
212
|
-
"""Get the current plan data."""
|
|
213
|
-
return self._session.current_plan
|
|
@@ -136,8 +136,10 @@ class UsageTracker(UsageTrackerProtocol):
|
|
|
136
136
|
last_cost_safe = last_cost if last_cost is not None else 0.0
|
|
137
137
|
session_cost_safe = session_cost if session_cost is not None else 0.0
|
|
138
138
|
|
|
139
|
+
total_tokens = prompt_safe + completion_safe
|
|
139
140
|
usage_summary = (
|
|
140
|
-
f"[ Tokens: {
|
|
141
|
+
f"[ Tokens: {total_tokens:,} "
|
|
142
|
+
f"(P: {prompt_safe:,}, C: {completion_safe:,}) | "
|
|
141
143
|
f"Cost: ${last_cost_safe:.4f} | "
|
|
142
144
|
f"Session Total: ${session_cost_safe:.4f} ]"
|
|
143
145
|
)
|