shotgun-sh 0.2.11.dev3__py3-none-any.whl → 0.2.11.dev7__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 +44 -1
- shotgun/agents/config/models.py +4 -0
- shotgun/agents/conversation_manager.py +14 -7
- shotgun/agents/history/history_processors.py +99 -3
- shotgun/agents/history/token_counting/openai.py +3 -1
- shotgun/exceptions.py +32 -0
- shotgun/sentry_telemetry.py +19 -1
- shotgun/tui/app.py +9 -14
- shotgun/tui/screens/chat/chat_screen.py +90 -26
- shotgun/tui/screens/chat_screen/command_providers.py +10 -0
- shotgun/tui/screens/github_issue.py +102 -0
- shotgun/tui/screens/onboarding.py +431 -0
- shotgun/tui/services/conversation_service.py +8 -6
- {shotgun_sh-0.2.11.dev3.dist-info → shotgun_sh-0.2.11.dev7.dist-info}/METADATA +1 -1
- {shotgun_sh-0.2.11.dev3.dist-info → shotgun_sh-0.2.11.dev7.dist-info}/RECORD +18 -15
- {shotgun_sh-0.2.11.dev3.dist-info → shotgun_sh-0.2.11.dev7.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.11.dev3.dist-info → shotgun_sh-0.2.11.dev7.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.11.dev3.dist-info → shotgun_sh-0.2.11.dev7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Screen for guiding users to create GitHub issues."""
|
|
2
|
+
|
|
3
|
+
import webbrowser
|
|
4
|
+
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Container, Vertical
|
|
8
|
+
from textual.screen import ModalScreen
|
|
9
|
+
from textual.widgets import Button, Markdown, Static
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitHubIssueScreen(ModalScreen[None]):
|
|
13
|
+
"""Guide users to create issues on GitHub."""
|
|
14
|
+
|
|
15
|
+
CSS = """
|
|
16
|
+
GitHubIssueScreen {
|
|
17
|
+
align: center middle;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#issue-container {
|
|
21
|
+
width: 70;
|
|
22
|
+
max-width: 100;
|
|
23
|
+
height: auto;
|
|
24
|
+
border: thick $primary;
|
|
25
|
+
background: $surface;
|
|
26
|
+
padding: 2;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#issue-header {
|
|
30
|
+
text-style: bold;
|
|
31
|
+
color: $text-accent;
|
|
32
|
+
padding-bottom: 1;
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#issue-content {
|
|
37
|
+
padding: 1 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#issue-buttons {
|
|
41
|
+
height: auto;
|
|
42
|
+
padding: 2 0 0 0;
|
|
43
|
+
align: center middle;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#issue-buttons Button {
|
|
47
|
+
margin: 1 1;
|
|
48
|
+
min-width: 20;
|
|
49
|
+
}
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
BINDINGS = [
|
|
53
|
+
("escape", "dismiss", "Close"),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
def compose(self) -> ComposeResult:
|
|
57
|
+
"""Compose the GitHub issue screen."""
|
|
58
|
+
with Container(id="issue-container"):
|
|
59
|
+
yield Static("Create a GitHub Issue", id="issue-header")
|
|
60
|
+
with Vertical(id="issue-content"):
|
|
61
|
+
yield Markdown(
|
|
62
|
+
"""
|
|
63
|
+
## Report Bugs or Request Features
|
|
64
|
+
|
|
65
|
+
We track all bugs, feature requests, and improvements on GitHub Issues.
|
|
66
|
+
|
|
67
|
+
### How to Create an Issue:
|
|
68
|
+
|
|
69
|
+
1. Click the button below to open our GitHub Issues page
|
|
70
|
+
2. Click **"New Issue"**
|
|
71
|
+
3. Choose a template:
|
|
72
|
+
- **Bug Report** - Report a bug or unexpected behavior
|
|
73
|
+
- **Feature Request** - Suggest new functionality
|
|
74
|
+
- **Documentation** - Report docs issues or improvements
|
|
75
|
+
4. Fill in the details and submit
|
|
76
|
+
|
|
77
|
+
We review all issues and will respond as soon as possible!
|
|
78
|
+
|
|
79
|
+
### Before Creating an Issue:
|
|
80
|
+
|
|
81
|
+
- Search existing issues to avoid duplicates
|
|
82
|
+
- Include steps to reproduce for bugs
|
|
83
|
+
- Be specific about what you'd like for feature requests
|
|
84
|
+
""",
|
|
85
|
+
id="issue-markdown",
|
|
86
|
+
)
|
|
87
|
+
with Vertical(id="issue-buttons"):
|
|
88
|
+
yield Button(
|
|
89
|
+
"🐙 Open GitHub Issues", id="github-button", variant="primary"
|
|
90
|
+
)
|
|
91
|
+
yield Button("Close", id="close-button")
|
|
92
|
+
|
|
93
|
+
@on(Button.Pressed, "#github-button")
|
|
94
|
+
def handle_github(self) -> None:
|
|
95
|
+
"""Open GitHub issues page in browser."""
|
|
96
|
+
webbrowser.open("https://github.com/shotgun-sh/shotgun/issues")
|
|
97
|
+
self.notify("Opening GitHub Issues in your browser...")
|
|
98
|
+
|
|
99
|
+
@on(Button.Pressed, "#close-button")
|
|
100
|
+
def handle_close(self) -> None:
|
|
101
|
+
"""Handle close button press."""
|
|
102
|
+
self.dismiss()
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"""Onboarding popup modal for first-time users."""
|
|
2
|
+
|
|
3
|
+
import webbrowser
|
|
4
|
+
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.containers import Container, Horizontal, VerticalScroll
|
|
8
|
+
from textual.screen import ModalScreen
|
|
9
|
+
from textual.widgets import Button, Markdown, Static
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OnboardingModal(ModalScreen[None]):
|
|
13
|
+
"""Multi-page onboarding modal for new users.
|
|
14
|
+
|
|
15
|
+
This modal presents helpful resources and tips for using Shotgun across
|
|
16
|
+
multiple pages. Users can navigate between pages using Next/Back buttons.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
CSS = """
|
|
20
|
+
OnboardingModal {
|
|
21
|
+
align: center middle;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#onboarding-container {
|
|
25
|
+
width: 95;
|
|
26
|
+
max-width: 100;
|
|
27
|
+
height: auto;
|
|
28
|
+
max-height: 90%;
|
|
29
|
+
border: thick $primary;
|
|
30
|
+
background: $surface;
|
|
31
|
+
padding: 2;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#progress-sidebar {
|
|
35
|
+
width: 26;
|
|
36
|
+
dock: left;
|
|
37
|
+
border-right: solid $primary;
|
|
38
|
+
padding: 1;
|
|
39
|
+
height: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#main-content {
|
|
43
|
+
width: 1fr;
|
|
44
|
+
height: auto;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#progress-header {
|
|
48
|
+
text-style: bold;
|
|
49
|
+
padding-bottom: 1;
|
|
50
|
+
color: $text-accent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.progress-item {
|
|
54
|
+
padding: 1 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.progress-item-current {
|
|
58
|
+
color: $accent;
|
|
59
|
+
text-style: bold;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.progress-item-visited {
|
|
63
|
+
color: $success;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.progress-item-unvisited {
|
|
67
|
+
color: $text-muted;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#onboarding-header {
|
|
71
|
+
text-style: bold;
|
|
72
|
+
color: $text-accent;
|
|
73
|
+
padding-bottom: 1;
|
|
74
|
+
text-align: center;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#onboarding-content {
|
|
78
|
+
height: 1fr;
|
|
79
|
+
padding: 1 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#page-indicator {
|
|
83
|
+
text-align: center;
|
|
84
|
+
color: $text-muted;
|
|
85
|
+
padding: 1 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#buttons-container {
|
|
89
|
+
height: auto;
|
|
90
|
+
padding: 1 0 0 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#navigation-buttons {
|
|
94
|
+
width: 100%;
|
|
95
|
+
height: auto;
|
|
96
|
+
align: center middle;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.nav-button {
|
|
100
|
+
margin: 0 1;
|
|
101
|
+
min-width: 12;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#resource-sections {
|
|
105
|
+
padding: 1 0;
|
|
106
|
+
height: auto;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#resource-sections Button {
|
|
110
|
+
width: 100%;
|
|
111
|
+
margin: 0 0 2 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#video-section {
|
|
115
|
+
padding: 0;
|
|
116
|
+
margin: 0 0 1 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#docs-section {
|
|
120
|
+
padding: 0;
|
|
121
|
+
margin: 2 0 1 0;
|
|
122
|
+
}
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
BINDINGS = [
|
|
126
|
+
("escape", "dismiss", "Close"),
|
|
127
|
+
("ctrl+c", "app.quit", "Quit"),
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
def __init__(self) -> None:
|
|
131
|
+
"""Initialize the onboarding modal."""
|
|
132
|
+
super().__init__()
|
|
133
|
+
self.current_page = 0
|
|
134
|
+
self.total_pages = 4
|
|
135
|
+
self.page_titles = [
|
|
136
|
+
"Getting Started",
|
|
137
|
+
"Discovering the 5 Modes",
|
|
138
|
+
"Prompting Better",
|
|
139
|
+
"Context Management!",
|
|
140
|
+
]
|
|
141
|
+
# Track which pages have been visited (in memory only)
|
|
142
|
+
self.visited_pages: set[int] = {0} # Start on page 0, so it's visited
|
|
143
|
+
|
|
144
|
+
def compose(self) -> ComposeResult:
|
|
145
|
+
"""Compose the onboarding modal."""
|
|
146
|
+
with Container(id="onboarding-container"):
|
|
147
|
+
# Left sidebar for progress tracking
|
|
148
|
+
with Container(id="progress-sidebar"):
|
|
149
|
+
yield Static("Progress", id="progress-header")
|
|
150
|
+
for i in range(self.total_pages):
|
|
151
|
+
yield Static(
|
|
152
|
+
f"{i + 1}. {self.page_titles[i]}",
|
|
153
|
+
id=f"progress-item-{i}",
|
|
154
|
+
classes="progress-item",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Main content area
|
|
158
|
+
with Container(id="main-content"):
|
|
159
|
+
yield Static("Welcome to Shotgun!", id="onboarding-header")
|
|
160
|
+
with VerticalScroll(id="onboarding-content"):
|
|
161
|
+
yield Markdown(id="page-content")
|
|
162
|
+
# Resource sections (only shown on page 1)
|
|
163
|
+
with Container(id="resource-sections"):
|
|
164
|
+
yield Markdown(
|
|
165
|
+
"### 🎥 Video Demo\nWatch our demo video to see Shotgun in action",
|
|
166
|
+
id="video-section",
|
|
167
|
+
)
|
|
168
|
+
yield Button(
|
|
169
|
+
"▶️ Watch Demo Video",
|
|
170
|
+
id="youtube-button",
|
|
171
|
+
variant="success",
|
|
172
|
+
)
|
|
173
|
+
yield Markdown(
|
|
174
|
+
"### 📖 Documentation\nRead the comprehensive usage guide for detailed instructions",
|
|
175
|
+
id="docs-section",
|
|
176
|
+
)
|
|
177
|
+
yield Button(
|
|
178
|
+
"📚 Read Usage Guide", id="usage-button", variant="primary"
|
|
179
|
+
)
|
|
180
|
+
yield Static(id="page-indicator")
|
|
181
|
+
with Container(id="buttons-container"):
|
|
182
|
+
with Horizontal(id="navigation-buttons"):
|
|
183
|
+
yield Button("Back", id="back-button", classes="nav-button")
|
|
184
|
+
yield Button(
|
|
185
|
+
"Next",
|
|
186
|
+
id="next-button",
|
|
187
|
+
classes="nav-button",
|
|
188
|
+
variant="primary",
|
|
189
|
+
)
|
|
190
|
+
yield Button("Close", id="close-button", classes="nav-button")
|
|
191
|
+
|
|
192
|
+
def on_mount(self) -> None:
|
|
193
|
+
"""Set up the modal after mounting."""
|
|
194
|
+
self.update_page()
|
|
195
|
+
|
|
196
|
+
def update_page(self) -> None:
|
|
197
|
+
"""Update the displayed page content and navigation buttons."""
|
|
198
|
+
# Mark current page as visited
|
|
199
|
+
self.visited_pages.add(self.current_page)
|
|
200
|
+
|
|
201
|
+
# Update page content
|
|
202
|
+
content_widget = self.query_one("#page-content", Markdown)
|
|
203
|
+
content_widget.update(self.get_page_content())
|
|
204
|
+
|
|
205
|
+
# Update page indicator
|
|
206
|
+
page_indicator = self.query_one("#page-indicator", Static)
|
|
207
|
+
page_indicator.update(f"Page {self.current_page + 1} of {self.total_pages}")
|
|
208
|
+
|
|
209
|
+
# Update progress sidebar
|
|
210
|
+
for i in range(self.total_pages):
|
|
211
|
+
progress_item = self.query_one(f"#progress-item-{i}", Static)
|
|
212
|
+
# Remove all progress classes first
|
|
213
|
+
progress_item.remove_class(
|
|
214
|
+
"progress-item-current",
|
|
215
|
+
"progress-item-visited",
|
|
216
|
+
"progress-item-unvisited",
|
|
217
|
+
)
|
|
218
|
+
# Add appropriate class
|
|
219
|
+
if i == self.current_page:
|
|
220
|
+
progress_item.add_class("progress-item-current")
|
|
221
|
+
progress_item.update(f"▶ {i + 1}. {self.page_titles[i]}")
|
|
222
|
+
elif i in self.visited_pages:
|
|
223
|
+
progress_item.add_class("progress-item-visited")
|
|
224
|
+
progress_item.update(f"✓ {i + 1}. {self.page_titles[i]}")
|
|
225
|
+
else:
|
|
226
|
+
progress_item.add_class("progress-item-unvisited")
|
|
227
|
+
progress_item.update(f" {i + 1}. {self.page_titles[i]}")
|
|
228
|
+
|
|
229
|
+
# Show/hide resource sections (only on page 1)
|
|
230
|
+
resource_sections = self.query_one("#resource-sections", Container)
|
|
231
|
+
resource_sections.display = self.current_page == 0
|
|
232
|
+
|
|
233
|
+
# Update button visibility and states
|
|
234
|
+
back_button = self.query_one("#back-button", Button)
|
|
235
|
+
next_button = self.query_one("#next-button", Button)
|
|
236
|
+
|
|
237
|
+
# Update back button label and state
|
|
238
|
+
if self.current_page == 0:
|
|
239
|
+
back_button.disabled = True
|
|
240
|
+
back_button.label = "Back"
|
|
241
|
+
else:
|
|
242
|
+
back_button.disabled = False
|
|
243
|
+
prev_title = self.page_titles[self.current_page - 1]
|
|
244
|
+
back_button.label = f"← {prev_title}"
|
|
245
|
+
|
|
246
|
+
# Update next button label
|
|
247
|
+
if self.current_page == self.total_pages - 1:
|
|
248
|
+
next_button.label = "Finish"
|
|
249
|
+
else:
|
|
250
|
+
next_title = self.page_titles[self.current_page + 1]
|
|
251
|
+
next_button.label = f"{next_title} (Next →)"
|
|
252
|
+
|
|
253
|
+
# Focus the appropriate button
|
|
254
|
+
if self.current_page == 0:
|
|
255
|
+
next_button.focus()
|
|
256
|
+
else:
|
|
257
|
+
next_button.focus()
|
|
258
|
+
|
|
259
|
+
# Scroll content to top
|
|
260
|
+
self.query_one("#onboarding-content", VerticalScroll).scroll_home(animate=False)
|
|
261
|
+
|
|
262
|
+
def get_page_content(self) -> str:
|
|
263
|
+
"""Get the content for the current page."""
|
|
264
|
+
if self.current_page == 0:
|
|
265
|
+
return self._page_1_resources()
|
|
266
|
+
elif self.current_page == 1:
|
|
267
|
+
return self._page_2_modes()
|
|
268
|
+
elif self.current_page == 2:
|
|
269
|
+
return self._page_3_prompts()
|
|
270
|
+
else:
|
|
271
|
+
return self._page_4_context_management()
|
|
272
|
+
|
|
273
|
+
def _page_1_resources(self) -> str:
|
|
274
|
+
"""Page 1: Helpful resources."""
|
|
275
|
+
return """
|
|
276
|
+
## Getting Started Resources
|
|
277
|
+
|
|
278
|
+
Here are some helpful resources to get you up to speed with Shotgun:
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def _page_2_modes(self) -> str:
|
|
282
|
+
"""Page 2: Explanation of the 5 modes."""
|
|
283
|
+
return """
|
|
284
|
+
## Understanding Shotgun's 5 Modes
|
|
285
|
+
|
|
286
|
+
Shotgun has 5 specialized modes, each designed for specific tasks. Each mode writes to its own dedicated file in `.shotgun/`:
|
|
287
|
+
|
|
288
|
+
### 🔬 Research Mode
|
|
289
|
+
Research topics with web search and synthesize findings. Perfect for gathering information and exploring new concepts.
|
|
290
|
+
|
|
291
|
+
**Writes to:** `.shotgun/research.md`
|
|
292
|
+
|
|
293
|
+
### 📝 Specify Mode
|
|
294
|
+
Create detailed specifications and requirements documents. Great for planning features and documenting requirements.
|
|
295
|
+
|
|
296
|
+
**Writes to:** `.shotgun/specification.md`
|
|
297
|
+
|
|
298
|
+
### 📋 Plan Mode
|
|
299
|
+
Create comprehensive, actionable plans with milestones. Ideal for breaking down large projects into manageable steps.
|
|
300
|
+
|
|
301
|
+
**Writes to:** `.shotgun/plan.md`
|
|
302
|
+
|
|
303
|
+
### ✅ Tasks Mode
|
|
304
|
+
Generate specific, actionable tasks from research and plans. Best for getting concrete next steps and action items.
|
|
305
|
+
|
|
306
|
+
**Writes to:** `.shotgun/tasks.md`
|
|
307
|
+
|
|
308
|
+
### 📤 Export Mode
|
|
309
|
+
Export artifacts and findings to various formats. Creates documentation like Claude.md (AI instructions), Agent.md (agent specs), PRDs, and other deliverables. Can write to any file in `.shotgun/` except the mode-specific files above.
|
|
310
|
+
|
|
311
|
+
**Writes to:** `.shotgun/Claude.md`, `.shotgun/Agent.md`, `.shotgun/PRD.md`, etc.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
**Tip:** You can switch between modes using `Shift+Tab` or `Ctrl+P` to open the command palette!
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def _page_3_prompts(self) -> str:
|
|
319
|
+
"""Page 3: Tips for better prompts."""
|
|
320
|
+
return """
|
|
321
|
+
## Writing Better Prompts
|
|
322
|
+
|
|
323
|
+
Here are some tips to get the best results from Shotgun:
|
|
324
|
+
|
|
325
|
+
### 1. Ask for Research First
|
|
326
|
+
Before jumping into a task, ask Shotgun to research the codebase or topic:
|
|
327
|
+
|
|
328
|
+
> "Can you research how authentication works in this codebase?"
|
|
329
|
+
|
|
330
|
+
### 2. Request Clarifying Questions
|
|
331
|
+
Let Shotgun ask you questions to better understand your needs:
|
|
332
|
+
|
|
333
|
+
> "I want to add user profiles. Please ask me clarifying questions before starting."
|
|
334
|
+
|
|
335
|
+
### 3. Be Specific About Context
|
|
336
|
+
Provide relevant context about what you're trying to accomplish:
|
|
337
|
+
|
|
338
|
+
> "I'm working on the payment flow. I need to add support for refunds."
|
|
339
|
+
|
|
340
|
+
### 4. Use the Right Mode
|
|
341
|
+
Switch to the appropriate mode for your task:
|
|
342
|
+
- Use **Research** for exploration
|
|
343
|
+
- Use **Specify** for requirements
|
|
344
|
+
- Use **Plan** for implementation strategy
|
|
345
|
+
- Use **Tasks** for actionable next steps
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
**Remember:** Shotgun works best when you give it context and let it ask questions!
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
def _page_4_context_management(self) -> str:
|
|
353
|
+
"""Page 4: Context management and conversation controls."""
|
|
354
|
+
return """
|
|
355
|
+
## Managing Conversation Context
|
|
356
|
+
|
|
357
|
+
As conversations grow, you may need to manage the context sent to the AI model.
|
|
358
|
+
|
|
359
|
+
### Clear Conversation
|
|
360
|
+
Completely start over with a fresh conversation.
|
|
361
|
+
|
|
362
|
+
**How to use:**
|
|
363
|
+
- Open Command Palette: `Ctrl+P`
|
|
364
|
+
- Type: "Clear Conversation"
|
|
365
|
+
- Confirm the action
|
|
366
|
+
|
|
367
|
+
**When to use:**
|
|
368
|
+
- Starting a completely new task or project
|
|
369
|
+
- When you want a clean slate
|
|
370
|
+
- Context has become too cluttered
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
### Compact Conversation
|
|
375
|
+
Intelligently compress the conversation history while preserving important context.
|
|
376
|
+
|
|
377
|
+
**How to use:**
|
|
378
|
+
- Open Command Palette: `Ctrl+P`
|
|
379
|
+
- Type: "Compact Conversation"
|
|
380
|
+
- Shotgun will compress older messages automatically
|
|
381
|
+
|
|
382
|
+
**When to use:**
|
|
383
|
+
- Conversation is getting long but you want to keep context
|
|
384
|
+
- Running into token limits
|
|
385
|
+
- Want to reduce costs while maintaining continuity
|
|
386
|
+
|
|
387
|
+
**What it does:**
|
|
388
|
+
- Summarizes older messages
|
|
389
|
+
- Keeps recent messages intact
|
|
390
|
+
- Preserves key information and decisions
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
**Tip:** Use `Ctrl+U` to view your current usage and see how much context you're using!
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
@on(Button.Pressed, "#back-button")
|
|
398
|
+
def handle_back(self) -> None:
|
|
399
|
+
"""Handle back button press."""
|
|
400
|
+
if self.current_page > 0:
|
|
401
|
+
self.current_page -= 1
|
|
402
|
+
self.update_page()
|
|
403
|
+
|
|
404
|
+
@on(Button.Pressed, "#next-button")
|
|
405
|
+
def handle_next(self) -> None:
|
|
406
|
+
"""Handle next/finish button press."""
|
|
407
|
+
if self.current_page < self.total_pages - 1:
|
|
408
|
+
self.current_page += 1
|
|
409
|
+
self.update_page()
|
|
410
|
+
else:
|
|
411
|
+
# On last page, finish closes the modal
|
|
412
|
+
self.dismiss()
|
|
413
|
+
|
|
414
|
+
@on(Button.Pressed, "#close-button")
|
|
415
|
+
def handle_close(self) -> None:
|
|
416
|
+
"""Handle close button press."""
|
|
417
|
+
self.dismiss()
|
|
418
|
+
|
|
419
|
+
@on(Button.Pressed, "#youtube-button")
|
|
420
|
+
def handle_youtube(self) -> None:
|
|
421
|
+
"""Open demo section in README."""
|
|
422
|
+
webbrowser.open(
|
|
423
|
+
"https://github.com/shotgun-sh/shotgun?tab=readme-ov-file#-demo"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
@on(Button.Pressed, "#usage-button")
|
|
427
|
+
def handle_usage_guide(self) -> None:
|
|
428
|
+
"""Open usage guide in browser."""
|
|
429
|
+
webbrowser.open(
|
|
430
|
+
"https://github.com/shotgun-sh/shotgun?tab=readme-ov-file#-usage"
|
|
431
|
+
)
|
|
@@ -8,6 +8,8 @@ import logging
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
+
import aiofiles.os
|
|
12
|
+
|
|
11
13
|
from shotgun.agents.conversation_history import ConversationHistory, ConversationState
|
|
12
14
|
from shotgun.agents.conversation_manager import ConversationManager
|
|
13
15
|
from shotgun.agents.models import AgentType
|
|
@@ -95,7 +97,7 @@ class ConversationService:
|
|
|
95
97
|
logger.exception(f"Failed to load conversation: {e}")
|
|
96
98
|
return None
|
|
97
99
|
|
|
98
|
-
def check_for_corrupted_conversation(self) -> bool:
|
|
100
|
+
async def check_for_corrupted_conversation(self) -> bool:
|
|
99
101
|
"""Check if a conversation backup exists (indicating corruption).
|
|
100
102
|
|
|
101
103
|
Returns:
|
|
@@ -104,7 +106,7 @@ class ConversationService:
|
|
|
104
106
|
backup_path = self.conversation_manager.conversation_path.with_suffix(
|
|
105
107
|
".json.backup"
|
|
106
108
|
)
|
|
107
|
-
return
|
|
109
|
+
return await aiofiles.os.path.exists(str(backup_path))
|
|
108
110
|
|
|
109
111
|
async def restore_conversation(
|
|
110
112
|
self,
|
|
@@ -127,7 +129,7 @@ class ConversationService:
|
|
|
127
129
|
|
|
128
130
|
if conversation is None:
|
|
129
131
|
# Check for corruption
|
|
130
|
-
if self.check_for_corrupted_conversation():
|
|
132
|
+
if await self.check_for_corrupted_conversation():
|
|
131
133
|
return (
|
|
132
134
|
False,
|
|
133
135
|
"⚠️ Previous session was corrupted and has been backed up. Starting fresh conversation.",
|
|
@@ -165,7 +167,7 @@ class ConversationService:
|
|
|
165
167
|
None,
|
|
166
168
|
)
|
|
167
169
|
|
|
168
|
-
def clear_conversation(self) -> bool:
|
|
170
|
+
async def clear_conversation(self) -> bool:
|
|
169
171
|
"""Clear the saved conversation file.
|
|
170
172
|
|
|
171
173
|
Returns:
|
|
@@ -173,8 +175,8 @@ class ConversationService:
|
|
|
173
175
|
"""
|
|
174
176
|
try:
|
|
175
177
|
conversation_path = self.conversation_manager.conversation_path
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
+
if await aiofiles.os.path.exists(str(conversation_path)):
|
|
179
|
+
await aiofiles.os.unlink(str(conversation_path))
|
|
178
180
|
logger.info("Conversation file cleared")
|
|
179
181
|
return True
|
|
180
182
|
except Exception as e:
|