shotgun-sh 0.2.11__py3-none-any.whl → 0.2.11.dev2__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 shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +28 -194
- shotgun/agents/common.py +8 -14
- shotgun/agents/config/manager.py +33 -64
- shotgun/agents/config/models.py +1 -25
- shotgun/agents/config/provider.py +2 -2
- shotgun/agents/context_analyzer/analyzer.py +24 -2
- shotgun/agents/conversation_manager.py +19 -35
- shotgun/agents/export.py +2 -2
- shotgun/agents/history/history_processors.py +3 -99
- shotgun/agents/history/token_counting/anthropic.py +1 -17
- shotgun/agents/history/token_counting/base.py +3 -14
- shotgun/agents/history/token_counting/openai.py +1 -11
- shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -8
- shotgun/agents/history/token_counting/tokenizer_cache.py +1 -3
- shotgun/agents/history/token_counting/utils.py +3 -0
- shotgun/agents/plan.py +2 -2
- shotgun/agents/research.py +3 -3
- shotgun/agents/specify.py +2 -2
- shotgun/agents/tasks.py +2 -2
- shotgun/agents/tools/codebase/file_read.py +2 -5
- shotgun/agents/tools/file_management.py +7 -11
- shotgun/agents/tools/web_search/__init__.py +8 -8
- shotgun/agents/tools/web_search/anthropic.py +2 -2
- shotgun/agents/tools/web_search/gemini.py +1 -1
- shotgun/agents/tools/web_search/openai.py +1 -1
- shotgun/agents/tools/web_search/utils.py +2 -2
- shotgun/agents/usage_manager.py +11 -16
- shotgun/build_constants.py +2 -2
- shotgun/cli/clear.py +1 -2
- shotgun/cli/compact.py +3 -3
- shotgun/cli/config.py +5 -8
- shotgun/cli/context.py +2 -2
- shotgun/cli/export.py +1 -1
- shotgun/cli/feedback.py +2 -4
- shotgun/cli/plan.py +1 -1
- shotgun/cli/research.py +1 -1
- shotgun/cli/specify.py +1 -1
- shotgun/cli/tasks.py +1 -1
- shotgun/codebase/core/change_detector.py +3 -5
- shotgun/codebase/core/code_retrieval.py +2 -4
- shotgun/codebase/core/ingestor.py +8 -10
- shotgun/codebase/core/manager.py +3 -3
- shotgun/codebase/core/nl_query.py +1 -1
- shotgun/logging_config.py +17 -10
- shotgun/main.py +1 -3
- shotgun/posthog_telemetry.py +4 -14
- shotgun/sentry_telemetry.py +2 -22
- shotgun/telemetry.py +1 -3
- shotgun/tui/app.py +65 -71
- shotgun/tui/components/context_indicator.py +0 -43
- shotgun/tui/containers.py +17 -15
- shotgun/tui/dependencies.py +2 -2
- shotgun/tui/screens/chat/chat_screen.py +40 -164
- shotgun/tui/screens/chat/help_text.py +15 -16
- shotgun/tui/screens/chat_screen/command_providers.py +0 -10
- shotgun/tui/screens/feedback.py +4 -4
- shotgun/tui/screens/model_picker.py +20 -21
- shotgun/tui/screens/provider_config.py +27 -50
- shotgun/tui/screens/shotgun_auth.py +2 -2
- shotgun/tui/screens/welcome.py +11 -14
- shotgun/tui/services/conversation_service.py +14 -16
- shotgun/tui/utils/mode_progress.py +7 -14
- shotgun/tui/widgets/widget_coordinator.py +0 -15
- shotgun/utils/file_system_utils.py +0 -19
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/METADATA +1 -2
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/RECORD +69 -73
- shotgun/exceptions.py +0 -32
- shotgun/tui/screens/github_issue.py +0 -102
- shotgun/tui/screens/onboarding.py +0 -431
- shotgun/utils/marketing.py +0 -110
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.11.dist-info → shotgun_sh-0.2.11.dev2.dist-info}/licenses/LICENSE +0 -0
shotgun/logging_config.py
CHANGED
|
@@ -3,15 +3,11 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import logging.handlers
|
|
5
5
|
import sys
|
|
6
|
-
from datetime import datetime, timezone
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
|
|
9
8
|
from shotgun.settings import settings
|
|
10
9
|
from shotgun.utils.env_utils import is_truthy
|
|
11
10
|
|
|
12
|
-
# Generate a single timestamp for this run to be used across all loggers
|
|
13
|
-
_RUN_TIMESTAMP = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
14
|
-
|
|
15
11
|
|
|
16
12
|
def get_log_directory() -> Path:
|
|
17
13
|
"""Get the log directory path, creating it if necessary.
|
|
@@ -70,7 +66,10 @@ def setup_logger(
|
|
|
70
66
|
logger = logging.getLogger(name)
|
|
71
67
|
|
|
72
68
|
# Check if we already have a file handler
|
|
73
|
-
has_file_handler = any(
|
|
69
|
+
has_file_handler = any(
|
|
70
|
+
isinstance(h, logging.handlers.TimedRotatingFileHandler)
|
|
71
|
+
for h in logger.handlers
|
|
72
|
+
)
|
|
74
73
|
|
|
75
74
|
# If we already have a file handler, just return the logger
|
|
76
75
|
if has_file_handler:
|
|
@@ -121,16 +120,21 @@ def setup_logger(
|
|
|
121
120
|
|
|
122
121
|
if file_logging_enabled:
|
|
123
122
|
try:
|
|
124
|
-
# Create file handler with
|
|
123
|
+
# Create file handler with rotation
|
|
125
124
|
log_dir = get_log_directory()
|
|
126
|
-
log_file = log_dir /
|
|
125
|
+
log_file = log_dir / "shotgun.log"
|
|
127
126
|
|
|
128
|
-
# Use
|
|
129
|
-
file_handler = logging.
|
|
127
|
+
# Use TimedRotatingFileHandler - rotates daily and keeps 7 days of logs
|
|
128
|
+
file_handler = logging.handlers.TimedRotatingFileHandler(
|
|
130
129
|
filename=log_file,
|
|
130
|
+
when="midnight", # Rotate at midnight
|
|
131
|
+
interval=1, # Every 1 day
|
|
132
|
+
backupCount=7, # Keep 7 days of logs
|
|
131
133
|
encoding="utf-8",
|
|
132
134
|
)
|
|
133
135
|
|
|
136
|
+
# Also set max file size (10MB) using RotatingFileHandler as fallback
|
|
137
|
+
# Note: We'll use TimedRotatingFileHandler which handles both time and size
|
|
134
138
|
file_handler.setLevel(getattr(logging, log_level))
|
|
135
139
|
|
|
136
140
|
# Use standard formatter for file (no colors)
|
|
@@ -185,7 +189,10 @@ def get_logger(name: str) -> logging.Logger:
|
|
|
185
189
|
logger = logging.getLogger(name)
|
|
186
190
|
|
|
187
191
|
# Check if we have a file handler already
|
|
188
|
-
has_file_handler = any(
|
|
192
|
+
has_file_handler = any(
|
|
193
|
+
isinstance(h, logging.handlers.TimedRotatingFileHandler)
|
|
194
|
+
for h in logger.handlers
|
|
195
|
+
)
|
|
189
196
|
|
|
190
197
|
# If no file handler, set up the logger (will add file handler)
|
|
191
198
|
if not has_file_handler:
|
shotgun/main.py
CHANGED
|
@@ -56,10 +56,8 @@ logger.debug("Logfire observability enabled: %s", _logfire_enabled)
|
|
|
56
56
|
|
|
57
57
|
# Initialize configuration
|
|
58
58
|
try:
|
|
59
|
-
import asyncio
|
|
60
|
-
|
|
61
59
|
config_manager = get_config_manager()
|
|
62
|
-
|
|
60
|
+
config_manager.load() # Ensure config is loaded at startup
|
|
63
61
|
except Exception as e:
|
|
64
62
|
logger.debug("Configuration initialization warning: %s", e)
|
|
65
63
|
|
shotgun/posthog_telemetry.py
CHANGED
|
@@ -59,10 +59,8 @@ def setup_posthog_observability() -> bool:
|
|
|
59
59
|
|
|
60
60
|
# Set user context with anonymous shotgun instance ID from config
|
|
61
61
|
try:
|
|
62
|
-
import asyncio
|
|
63
|
-
|
|
64
62
|
config_manager = get_config_manager()
|
|
65
|
-
shotgun_instance_id =
|
|
63
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
66
64
|
|
|
67
65
|
# Identify the user in PostHog
|
|
68
66
|
posthog.identify( # type: ignore[attr-defined]
|
|
@@ -109,11 +107,9 @@ def track_event(event_name: str, properties: dict[str, Any] | None = None) -> No
|
|
|
109
107
|
return
|
|
110
108
|
|
|
111
109
|
try:
|
|
112
|
-
import asyncio
|
|
113
|
-
|
|
114
110
|
# Get shotgun instance ID for tracking
|
|
115
111
|
config_manager = get_config_manager()
|
|
116
|
-
shotgun_instance_id =
|
|
112
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
117
113
|
|
|
118
114
|
# Add version and environment to properties
|
|
119
115
|
if properties is None:
|
|
@@ -172,16 +168,10 @@ def submit_feedback_survey(feedback: Feedback) -> None:
|
|
|
172
168
|
logger.debug("PostHog not initialized, skipping feedback survey")
|
|
173
169
|
return
|
|
174
170
|
|
|
175
|
-
import asyncio
|
|
176
|
-
|
|
177
171
|
config_manager = get_config_manager()
|
|
178
|
-
config =
|
|
172
|
+
config = config_manager.load()
|
|
179
173
|
conversation_manager = ConversationManager()
|
|
180
|
-
conversation =
|
|
181
|
-
try:
|
|
182
|
-
conversation = asyncio.run(conversation_manager.load())
|
|
183
|
-
except Exception as e:
|
|
184
|
-
logger.debug(f"Failed to load conversation history: {e}")
|
|
174
|
+
conversation = conversation_manager.load()
|
|
185
175
|
last_10_messages = []
|
|
186
176
|
if conversation is not None:
|
|
187
177
|
last_10_messages = conversation.get_agent_messages()[:10]
|
shotgun/sentry_telemetry.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Sentry observability setup for Shotgun."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
3
|
from shotgun import __version__
|
|
6
4
|
from shotgun.logging_config import get_early_logger
|
|
7
5
|
from shotgun.settings import settings
|
|
@@ -34,27 +32,12 @@ def setup_sentry_observability() -> bool:
|
|
|
34
32
|
logger.debug("Using Sentry DSN from settings, proceeding with setup")
|
|
35
33
|
|
|
36
34
|
# Determine environment based on version
|
|
37
|
-
# Dev versions contain "dev", "rc", "alpha", "beta"
|
|
35
|
+
# Dev versions contain "dev", "rc", "alpha", or "beta"
|
|
38
36
|
if any(marker in __version__ for marker in ["dev", "rc", "alpha", "beta"]):
|
|
39
37
|
environment = "development"
|
|
40
38
|
else:
|
|
41
39
|
environment = "production"
|
|
42
40
|
|
|
43
|
-
def before_send(event: Any, hint: dict[str, Any]) -> Any:
|
|
44
|
-
"""Filter out user-actionable errors from Sentry.
|
|
45
|
-
|
|
46
|
-
User-actionable errors (like context size limits) are expected conditions
|
|
47
|
-
that users need to resolve, not bugs that need tracking.
|
|
48
|
-
"""
|
|
49
|
-
if "exc_info" in hint:
|
|
50
|
-
exc_type, exc_value, tb = hint["exc_info"]
|
|
51
|
-
from shotgun.exceptions import ErrorNotPickedUpBySentry
|
|
52
|
-
|
|
53
|
-
if isinstance(exc_value, ErrorNotPickedUpBySentry):
|
|
54
|
-
# Don't send to Sentry - this is user-actionable, not a bug
|
|
55
|
-
return None
|
|
56
|
-
return event
|
|
57
|
-
|
|
58
41
|
# Initialize Sentry
|
|
59
42
|
sentry_sdk.init(
|
|
60
43
|
dsn=dsn,
|
|
@@ -63,17 +46,14 @@ def setup_sentry_observability() -> bool:
|
|
|
63
46
|
send_default_pii=False, # Privacy-first: never send PII
|
|
64
47
|
traces_sample_rate=0.1 if environment == "production" else 1.0,
|
|
65
48
|
profiles_sample_rate=0.1 if environment == "production" else 1.0,
|
|
66
|
-
before_send=before_send,
|
|
67
49
|
)
|
|
68
50
|
|
|
69
51
|
# Set user context with anonymous shotgun instance ID from config
|
|
70
52
|
try:
|
|
71
|
-
import asyncio
|
|
72
|
-
|
|
73
53
|
from shotgun.agents.config import get_config_manager
|
|
74
54
|
|
|
75
55
|
config_manager = get_config_manager()
|
|
76
|
-
shotgun_instance_id =
|
|
56
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
77
57
|
sentry_sdk.set_user({"id": shotgun_instance_id})
|
|
78
58
|
logger.debug("Sentry user context set with anonymous ID")
|
|
79
59
|
except Exception as e:
|
shotgun/telemetry.py
CHANGED
|
@@ -50,14 +50,12 @@ def setup_logfire_observability() -> bool:
|
|
|
50
50
|
|
|
51
51
|
# Set user context using baggage for all logs and spans
|
|
52
52
|
try:
|
|
53
|
-
import asyncio
|
|
54
|
-
|
|
55
53
|
from opentelemetry import baggage, context
|
|
56
54
|
|
|
57
55
|
from shotgun.agents.config import get_config_manager
|
|
58
56
|
|
|
59
57
|
config_manager = get_config_manager()
|
|
60
|
-
shotgun_instance_id =
|
|
58
|
+
shotgun_instance_id = config_manager.get_shotgun_instance_id()
|
|
61
59
|
|
|
62
60
|
# Set shotgun_instance_id as baggage in global context - this will be included in all logs/spans
|
|
63
61
|
ctx = baggage.set_baggage("shotgun_instance_id", shotgun_instance_id)
|
shotgun/tui/app.py
CHANGED
|
@@ -5,7 +5,6 @@ from textual.app import App, SystemCommand
|
|
|
5
5
|
from textual.binding import Binding
|
|
6
6
|
from textual.screen import Screen
|
|
7
7
|
|
|
8
|
-
from shotgun.agents.agent_manager import AgentManager
|
|
9
8
|
from shotgun.agents.config import ConfigManager, get_config_manager
|
|
10
9
|
from shotgun.agents.models import AgentType
|
|
11
10
|
from shotgun.logging_config import get_logger
|
|
@@ -19,7 +18,7 @@ from shotgun.utils.update_checker import (
|
|
|
19
18
|
|
|
20
19
|
from .screens.chat import ChatScreen
|
|
21
20
|
from .screens.directory_setup import DirectorySetupScreen
|
|
22
|
-
from .screens.
|
|
21
|
+
from .screens.feedback import FeedbackScreen
|
|
23
22
|
from .screens.model_picker import ModelPickerScreen
|
|
24
23
|
from .screens.pipx_migration import PipxMigrationScreen
|
|
25
24
|
from .screens.provider_config import ProviderConfigScreen
|
|
@@ -35,7 +34,7 @@ class ShotgunApp(App[None]):
|
|
|
35
34
|
"provider_config": ProviderConfigScreen,
|
|
36
35
|
"model_picker": ModelPickerScreen,
|
|
37
36
|
"directory_setup": DirectorySetupScreen,
|
|
38
|
-
"
|
|
37
|
+
"feedback": FeedbackScreen,
|
|
39
38
|
}
|
|
40
39
|
BINDINGS = [
|
|
41
40
|
Binding("ctrl+c", "quit", "Quit the app"),
|
|
@@ -96,75 +95,65 @@ class ShotgunApp(App[None]):
|
|
|
96
95
|
)
|
|
97
96
|
return
|
|
98
97
|
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
self.push_screen(
|
|
109
|
-
WelcomeScreen(),
|
|
110
|
-
callback=lambda _arg: self.refresh_startup_screen(),
|
|
111
|
-
)
|
|
98
|
+
# Show welcome screen if no providers are configured OR if user hasn't seen it yet
|
|
99
|
+
config = self.config_manager.load()
|
|
100
|
+
if (
|
|
101
|
+
not self.config_manager.has_any_provider_key()
|
|
102
|
+
or not config.shown_welcome_screen
|
|
103
|
+
):
|
|
104
|
+
if isinstance(self.screen, WelcomeScreen):
|
|
112
105
|
return
|
|
113
106
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
DirectorySetupScreen(),
|
|
120
|
-
callback=lambda _arg: self.refresh_startup_screen(),
|
|
121
|
-
)
|
|
122
|
-
return
|
|
107
|
+
self.push_screen(
|
|
108
|
+
WelcomeScreen(),
|
|
109
|
+
callback=lambda _arg: self.refresh_startup_screen(),
|
|
110
|
+
)
|
|
111
|
+
return
|
|
123
112
|
|
|
124
|
-
|
|
113
|
+
if not self.check_local_shotgun_directory_exists():
|
|
114
|
+
if isinstance(self.screen, DirectorySetupScreen):
|
|
125
115
|
return
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
# Create AgentDeps asynchronously (get_provider_model is now async)
|
|
132
|
-
from shotgun.tui.dependencies import create_default_tui_deps
|
|
133
|
-
|
|
134
|
-
agent_deps = await create_default_tui_deps()
|
|
135
|
-
|
|
136
|
-
# Create AgentManager with async initialization
|
|
137
|
-
agent_manager = AgentManager(deps=agent_deps, initial_type=agent_mode)
|
|
138
|
-
|
|
139
|
-
# Create ProcessingStateManager - we'll pass the screen after creation
|
|
140
|
-
# For now, create with None and the ChatScreen will set itself
|
|
141
|
-
chat_screen = ChatScreen(
|
|
142
|
-
agent_manager=agent_manager,
|
|
143
|
-
conversation_manager=self.container.conversation_manager(),
|
|
144
|
-
conversation_service=self.container.conversation_service(),
|
|
145
|
-
widget_coordinator=self.container.widget_coordinator_factory(
|
|
146
|
-
screen=None
|
|
147
|
-
),
|
|
148
|
-
processing_state=self.container.processing_state_factory(
|
|
149
|
-
screen=None, # Will be set after ChatScreen is created
|
|
150
|
-
telemetry_context={"agent_mode": agent_mode.value},
|
|
151
|
-
),
|
|
152
|
-
command_handler=self.container.command_handler(),
|
|
153
|
-
placeholder_hints=self.container.placeholder_hints(),
|
|
154
|
-
codebase_sdk=self.container.codebase_sdk(),
|
|
155
|
-
deps=agent_deps,
|
|
156
|
-
continue_session=self.continue_session,
|
|
157
|
-
force_reindex=self.force_reindex,
|
|
117
|
+
self.push_screen(
|
|
118
|
+
DirectorySetupScreen(),
|
|
119
|
+
callback=lambda _arg: self.refresh_startup_screen(),
|
|
158
120
|
)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if isinstance(self.screen, ChatScreen):
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# Create ChatScreen with all dependencies injected from container
|
|
127
|
+
# Get the default agent mode (RESEARCH)
|
|
128
|
+
agent_mode = AgentType.RESEARCH
|
|
129
|
+
|
|
130
|
+
# Create AgentManager with the correct mode
|
|
131
|
+
agent_manager = self.container.agent_manager_factory(initial_type=agent_mode)
|
|
132
|
+
|
|
133
|
+
# Create ProcessingStateManager - we'll pass the screen after creation
|
|
134
|
+
# For now, create with None and the ChatScreen will set itself
|
|
135
|
+
chat_screen = ChatScreen(
|
|
136
|
+
agent_manager=agent_manager,
|
|
137
|
+
conversation_manager=self.container.conversation_manager(),
|
|
138
|
+
conversation_service=self.container.conversation_service(),
|
|
139
|
+
widget_coordinator=self.container.widget_coordinator_factory(screen=None),
|
|
140
|
+
processing_state=self.container.processing_state_factory(
|
|
141
|
+
screen=None, # Will be set after ChatScreen is created
|
|
142
|
+
telemetry_context={"agent_mode": agent_mode.value},
|
|
143
|
+
),
|
|
144
|
+
command_handler=self.container.command_handler(),
|
|
145
|
+
placeholder_hints=self.container.placeholder_hints(),
|
|
146
|
+
codebase_sdk=self.container.codebase_sdk(),
|
|
147
|
+
deps=self.container.agent_deps(),
|
|
148
|
+
continue_session=self.continue_session,
|
|
149
|
+
force_reindex=self.force_reindex,
|
|
150
|
+
)
|
|
159
151
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
self.push_screen(chat_screen)
|
|
152
|
+
# Update the ProcessingStateManager and WidgetCoordinator with the actual ChatScreen instance
|
|
153
|
+
chat_screen.processing_state.screen = chat_screen
|
|
154
|
+
chat_screen.widget_coordinator.screen = chat_screen
|
|
165
155
|
|
|
166
|
-
|
|
167
|
-
self.run_worker(_check_config(), exclusive=False)
|
|
156
|
+
self.push_screen(chat_screen)
|
|
168
157
|
|
|
169
158
|
def check_local_shotgun_directory_exists(self) -> bool:
|
|
170
159
|
shotgun_dir = get_shotgun_base_path()
|
|
@@ -181,15 +170,20 @@ class ShotgunApp(App[None]):
|
|
|
181
170
|
def get_system_commands(self, screen: Screen[Any]) -> Iterable[SystemCommand]:
|
|
182
171
|
return [
|
|
183
172
|
SystemCommand(
|
|
184
|
-
"
|
|
185
|
-
"Report a bug or request a feature on GitHub",
|
|
186
|
-
self.action_new_issue,
|
|
173
|
+
"Feedback", "Send us feedback or report a bug", self.action_feedback
|
|
187
174
|
)
|
|
188
|
-
]
|
|
175
|
+
] # we don't want any system commands
|
|
176
|
+
|
|
177
|
+
def action_feedback(self) -> None:
|
|
178
|
+
"""Open feedback screen and submit feedback."""
|
|
179
|
+
from shotgun.posthog_telemetry import Feedback, submit_feedback_survey
|
|
180
|
+
|
|
181
|
+
def handle_feedback(feedback: Feedback | None) -> None:
|
|
182
|
+
if feedback is not None:
|
|
183
|
+
submit_feedback_survey(feedback)
|
|
184
|
+
self.notify("Feedback sent. Thank you!")
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
"""Open GitHub issue screen to guide users to create an issue."""
|
|
192
|
-
self.push_screen(GitHubIssueScreen())
|
|
186
|
+
self.push_screen(FeedbackScreen(), callback=handle_feedback)
|
|
193
187
|
|
|
194
188
|
|
|
195
189
|
def run(
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Context window indicator component for showing model usage."""
|
|
2
2
|
|
|
3
3
|
from textual.reactive import reactive
|
|
4
|
-
from textual.timer import Timer
|
|
5
4
|
from textual.widgets import Static
|
|
6
5
|
|
|
7
6
|
from shotgun.agents.config.models import MODEL_SPECS, ModelName
|
|
@@ -21,10 +20,6 @@ class ContextIndicator(Static):
|
|
|
21
20
|
|
|
22
21
|
context_analysis: reactive[ContextAnalysis | None] = reactive(None)
|
|
23
22
|
model_name: reactive[ModelName | None] = reactive(None)
|
|
24
|
-
is_streaming: reactive[bool] = reactive(False)
|
|
25
|
-
|
|
26
|
-
_animation_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
27
|
-
_animation_index = 0
|
|
28
23
|
|
|
29
24
|
def __init__(
|
|
30
25
|
self,
|
|
@@ -34,7 +29,6 @@ class ContextIndicator(Static):
|
|
|
34
29
|
classes: str | None = None,
|
|
35
30
|
) -> None:
|
|
36
31
|
super().__init__(name=name, id=id, classes=classes)
|
|
37
|
-
self._animation_timer: Timer | None = None
|
|
38
32
|
|
|
39
33
|
def update_context(
|
|
40
34
|
self, analysis: ContextAnalysis | None, model: ModelName | None
|
|
@@ -49,38 +43,6 @@ class ContextIndicator(Static):
|
|
|
49
43
|
self.model_name = model
|
|
50
44
|
self._refresh_display()
|
|
51
45
|
|
|
52
|
-
def set_streaming(self, streaming: bool) -> None:
|
|
53
|
-
"""Enable or disable streaming animation.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
streaming: Whether to show streaming animation
|
|
57
|
-
"""
|
|
58
|
-
self.is_streaming = streaming
|
|
59
|
-
if streaming:
|
|
60
|
-
self._start_animation()
|
|
61
|
-
else:
|
|
62
|
-
self._stop_animation()
|
|
63
|
-
|
|
64
|
-
def _start_animation(self) -> None:
|
|
65
|
-
"""Start the pulsing animation."""
|
|
66
|
-
if self._animation_timer is None:
|
|
67
|
-
self._animation_timer = self.set_interval(0.1, self._animate_frame)
|
|
68
|
-
|
|
69
|
-
def _stop_animation(self) -> None:
|
|
70
|
-
"""Stop the pulsing animation."""
|
|
71
|
-
if self._animation_timer is not None:
|
|
72
|
-
self._animation_timer.stop()
|
|
73
|
-
self._animation_timer = None
|
|
74
|
-
self._animation_index = 0
|
|
75
|
-
self._refresh_display()
|
|
76
|
-
|
|
77
|
-
def _animate_frame(self) -> None:
|
|
78
|
-
"""Advance the animation frame."""
|
|
79
|
-
self._animation_index = (self._animation_index + 1) % len(
|
|
80
|
-
self._animation_frames
|
|
81
|
-
)
|
|
82
|
-
self._refresh_display()
|
|
83
|
-
|
|
84
46
|
def _get_percentage_color(self, percentage: float) -> str:
|
|
85
47
|
"""Get color for percentage based on threshold.
|
|
86
48
|
|
|
@@ -150,11 +112,6 @@ class ContextIndicator(Static):
|
|
|
150
112
|
f"[{color}]{percentage}% ({current_tokens}/{max_tokens})[/]",
|
|
151
113
|
]
|
|
152
114
|
|
|
153
|
-
# Add streaming animation indicator if streaming
|
|
154
|
-
if self.is_streaming:
|
|
155
|
-
animation_char = self._animation_frames[self._animation_index]
|
|
156
|
-
parts.append(f"[bold cyan]{animation_char}[/]")
|
|
157
|
-
|
|
158
115
|
# Add model name if available
|
|
159
116
|
if self.model_name:
|
|
160
117
|
model_spec = MODEL_SPECS.get(self.model_name)
|
shotgun/tui/containers.py
CHANGED
|
@@ -5,8 +5,10 @@ from typing import TYPE_CHECKING
|
|
|
5
5
|
from dependency_injector import containers, providers
|
|
6
6
|
from pydantic_ai import RunContext
|
|
7
7
|
|
|
8
|
+
from shotgun.agents.agent_manager import AgentManager
|
|
9
|
+
from shotgun.agents.config import get_provider_model
|
|
8
10
|
from shotgun.agents.conversation_manager import ConversationManager
|
|
9
|
-
from shotgun.agents.models import AgentDeps
|
|
11
|
+
from shotgun.agents.models import AgentDeps, AgentType
|
|
10
12
|
from shotgun.sdk.codebase import CodebaseSDK
|
|
11
13
|
from shotgun.tui.commands import CommandHandler
|
|
12
14
|
from shotgun.tui.filtered_codebase_service import FilteredCodebaseService
|
|
@@ -33,19 +35,13 @@ class TUIContainer(containers.DeclarativeContainer):
|
|
|
33
35
|
|
|
34
36
|
This container manages the lifecycle and dependencies of all TUI components,
|
|
35
37
|
ensuring consistent configuration and facilitating testing.
|
|
36
|
-
|
|
37
|
-
Note: model_config and agent_deps are created lazily via async factory methods
|
|
38
|
-
since get_provider_model() is now async.
|
|
39
38
|
"""
|
|
40
39
|
|
|
41
40
|
# Configuration
|
|
42
41
|
config = providers.Configuration()
|
|
43
42
|
|
|
44
43
|
# Core dependencies
|
|
45
|
-
|
|
46
|
-
# model_config is now loaded lazily via create_default_tui_deps()
|
|
47
|
-
# because get_provider_model() is async. This breaks the DI pattern
|
|
48
|
-
# and should be refactored to support async factories properly.
|
|
44
|
+
model_config = providers.Singleton(get_provider_model)
|
|
49
45
|
|
|
50
46
|
storage_dir = providers.Singleton(lambda: get_shotgun_home() / "codebases")
|
|
51
47
|
|
|
@@ -55,10 +51,15 @@ class TUIContainer(containers.DeclarativeContainer):
|
|
|
55
51
|
|
|
56
52
|
system_prompt_fn = providers.Object(_placeholder_system_prompt)
|
|
57
53
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
# AgentDeps singleton
|
|
55
|
+
agent_deps = providers.Singleton(
|
|
56
|
+
AgentDeps,
|
|
57
|
+
interactive_mode=True,
|
|
58
|
+
is_tui_context=True,
|
|
59
|
+
llm_model=model_config,
|
|
60
|
+
codebase_service=codebase_service,
|
|
61
|
+
system_prompt_fn=system_prompt_fn,
|
|
62
|
+
)
|
|
62
63
|
|
|
63
64
|
# Service singletons
|
|
64
65
|
codebase_sdk = providers.Singleton(CodebaseSDK)
|
|
@@ -73,9 +74,10 @@ class TUIContainer(containers.DeclarativeContainer):
|
|
|
73
74
|
ConversationService, conversation_manager=conversation_manager
|
|
74
75
|
)
|
|
75
76
|
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
# Factory for AgentManager (needs agent_type parameter)
|
|
78
|
+
agent_manager_factory = providers.Factory(
|
|
79
|
+
AgentManager, deps=agent_deps, initial_type=providers.Object(AgentType.RESEARCH)
|
|
80
|
+
)
|
|
79
81
|
|
|
80
82
|
# Factory for ProcessingStateManager (needs ChatScreen reference)
|
|
81
83
|
processing_state_factory = providers.Factory(
|
shotgun/tui/dependencies.py
CHANGED
|
@@ -8,7 +8,7 @@ from shotgun.tui.filtered_codebase_service import FilteredCodebaseService
|
|
|
8
8
|
from shotgun.utils import get_shotgun_home
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
def create_default_tui_deps() -> AgentDeps:
|
|
12
12
|
"""Create default AgentDeps for TUI components.
|
|
13
13
|
|
|
14
14
|
This creates a standard AgentDeps configuration suitable for interactive
|
|
@@ -21,7 +21,7 @@ async def create_default_tui_deps() -> AgentDeps:
|
|
|
21
21
|
Returns:
|
|
22
22
|
Configured AgentDeps instance ready for TUI use.
|
|
23
23
|
"""
|
|
24
|
-
model_config =
|
|
24
|
+
model_config = get_provider_model()
|
|
25
25
|
storage_dir = get_shotgun_home() / "codebases"
|
|
26
26
|
codebase_service = FilteredCodebaseService(storage_dir)
|
|
27
27
|
|