shotgun-sh 0.2.5.dev1__py3-none-any.whl → 0.2.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 shotgun-sh might be problematic. Click here for more details.
- shotgun/agents/agent_manager.py +103 -44
- shotgun/agents/common.py +13 -45
- shotgun/agents/config/manager.py +4 -0
- shotgun/agents/export.py +4 -5
- shotgun/agents/history/token_counting/anthropic.py +22 -2
- shotgun/agents/models.py +50 -2
- shotgun/agents/plan.py +4 -5
- shotgun/agents/research.py +4 -5
- shotgun/agents/specify.py +4 -5
- shotgun/agents/tasks.py +4 -5
- shotgun/agents/tools/__init__.py +0 -2
- shotgun/build_constants.py +2 -2
- shotgun/prompts/agents/export.j2 +18 -1
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +5 -1
- shotgun/prompts/agents/partials/interactive_mode.j2 +24 -7
- shotgun/prompts/agents/plan.j2 +1 -1
- shotgun/prompts/agents/research.j2 +1 -1
- shotgun/prompts/agents/tasks.j2 +1 -1
- shotgun/telemetry.py +8 -0
- shotgun/tui/screens/chat.py +185 -34
- shotgun/tui/screens/chat_screen/history.py +59 -76
- shotgun/tui/screens/welcome.py +10 -2
- shotgun_sh-0.2.6.dist-info/METADATA +125 -0
- {shotgun_sh-0.2.5.dev1.dist-info → shotgun_sh-0.2.6.dist-info}/RECORD +27 -28
- shotgun/agents/tools/user_interaction.py +0 -37
- shotgun_sh-0.2.5.dev1.dist-info/METADATA +0 -467
- {shotgun_sh-0.2.5.dev1.dist-info → shotgun_sh-0.2.6.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.2.5.dev1.dist-info → shotgun_sh-0.2.6.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.2.5.dev1.dist-info → shotgun_sh-0.2.6.dist-info}/licenses/LICENSE +0 -0
shotgun/agents/specify.py
CHANGED
|
@@ -4,7 +4,6 @@ from functools import partial
|
|
|
4
4
|
|
|
5
5
|
from pydantic_ai import (
|
|
6
6
|
Agent,
|
|
7
|
-
DeferredToolRequests,
|
|
8
7
|
)
|
|
9
8
|
from pydantic_ai.agent import AgentRunResult
|
|
10
9
|
from pydantic_ai.messages import ModelMessage
|
|
@@ -19,14 +18,14 @@ from .common import (
|
|
|
19
18
|
create_usage_limits,
|
|
20
19
|
run_agent,
|
|
21
20
|
)
|
|
22
|
-
from .models import AgentDeps, AgentRuntimeOptions, AgentType
|
|
21
|
+
from .models import AgentDeps, AgentResponse, AgentRuntimeOptions, AgentType
|
|
23
22
|
|
|
24
23
|
logger = get_logger(__name__)
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def create_specify_agent(
|
|
28
27
|
agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
|
|
29
|
-
) -> tuple[Agent[AgentDeps,
|
|
28
|
+
) -> tuple[Agent[AgentDeps, AgentResponse], AgentDeps]:
|
|
30
29
|
"""Create a specify agent with artifact management capabilities.
|
|
31
30
|
|
|
32
31
|
Args:
|
|
@@ -52,11 +51,11 @@ def create_specify_agent(
|
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
async def run_specify_agent(
|
|
55
|
-
agent: Agent[AgentDeps,
|
|
54
|
+
agent: Agent[AgentDeps, AgentResponse],
|
|
56
55
|
requirement: str,
|
|
57
56
|
deps: AgentDeps,
|
|
58
57
|
message_history: list[ModelMessage] | None = None,
|
|
59
|
-
) -> AgentRunResult[
|
|
58
|
+
) -> AgentRunResult[AgentResponse]:
|
|
60
59
|
"""Create or update specifications based on the given requirement.
|
|
61
60
|
|
|
62
61
|
Args:
|
shotgun/agents/tasks.py
CHANGED
|
@@ -4,7 +4,6 @@ from functools import partial
|
|
|
4
4
|
|
|
5
5
|
from pydantic_ai import (
|
|
6
6
|
Agent,
|
|
7
|
-
DeferredToolRequests,
|
|
8
7
|
)
|
|
9
8
|
from pydantic_ai.agent import AgentRunResult
|
|
10
9
|
from pydantic_ai.messages import ModelMessage
|
|
@@ -19,14 +18,14 @@ from .common import (
|
|
|
19
18
|
create_usage_limits,
|
|
20
19
|
run_agent,
|
|
21
20
|
)
|
|
22
|
-
from .models import AgentDeps, AgentRuntimeOptions, AgentType
|
|
21
|
+
from .models import AgentDeps, AgentResponse, AgentRuntimeOptions, AgentType
|
|
23
22
|
|
|
24
23
|
logger = get_logger(__name__)
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def create_tasks_agent(
|
|
28
27
|
agent_runtime_options: AgentRuntimeOptions, provider: ProviderType | None = None
|
|
29
|
-
) -> tuple[Agent[AgentDeps,
|
|
28
|
+
) -> tuple[Agent[AgentDeps, AgentResponse], AgentDeps]:
|
|
30
29
|
"""Create a tasks agent with file management capabilities.
|
|
31
30
|
|
|
32
31
|
Args:
|
|
@@ -50,11 +49,11 @@ def create_tasks_agent(
|
|
|
50
49
|
|
|
51
50
|
|
|
52
51
|
async def run_tasks_agent(
|
|
53
|
-
agent: Agent[AgentDeps,
|
|
52
|
+
agent: Agent[AgentDeps, AgentResponse],
|
|
54
53
|
instruction: str,
|
|
55
54
|
deps: AgentDeps,
|
|
56
55
|
message_history: list[ModelMessage] | None = None,
|
|
57
|
-
) -> AgentRunResult[
|
|
56
|
+
) -> AgentRunResult[AgentResponse]:
|
|
58
57
|
"""Create or update tasks based on the given instruction.
|
|
59
58
|
|
|
60
59
|
Args:
|
shotgun/agents/tools/__init__.py
CHANGED
|
@@ -8,7 +8,6 @@ from .codebase import (
|
|
|
8
8
|
retrieve_code,
|
|
9
9
|
)
|
|
10
10
|
from .file_management import append_file, read_file, write_file
|
|
11
|
-
from .user_interaction import ask_user
|
|
12
11
|
from .web_search import (
|
|
13
12
|
anthropic_web_search_tool,
|
|
14
13
|
gemini_web_search_tool,
|
|
@@ -21,7 +20,6 @@ __all__ = [
|
|
|
21
20
|
"anthropic_web_search_tool",
|
|
22
21
|
"gemini_web_search_tool",
|
|
23
22
|
"get_available_web_search_tools",
|
|
24
|
-
"ask_user",
|
|
25
23
|
"read_file",
|
|
26
24
|
"write_file",
|
|
27
25
|
"append_file",
|
shotgun/build_constants.py
CHANGED
|
@@ -12,8 +12,8 @@ POSTHOG_API_KEY = ''
|
|
|
12
12
|
POSTHOG_PROJECT_ID = '191396'
|
|
13
13
|
|
|
14
14
|
# Logfire configuration embedded at build time (only for dev builds)
|
|
15
|
-
LOGFIRE_ENABLED = '
|
|
16
|
-
LOGFIRE_TOKEN = '
|
|
15
|
+
LOGFIRE_ENABLED = ''
|
|
16
|
+
LOGFIRE_TOKEN = ''
|
|
17
17
|
|
|
18
18
|
# Build metadata
|
|
19
19
|
BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
|
shotgun/prompts/agents/export.j2
CHANGED
|
@@ -124,6 +124,7 @@ content_tasks = read_file('tasks.md') # Read implementation details
|
|
|
124
124
|
- `plan.md` - Extract development approach and stages
|
|
125
125
|
- `tasks.md` - Understand implementation tasks and structure
|
|
126
126
|
2. **Map content to agents.md standard sections**:
|
|
127
|
+
- **Research, Specifications, and Planning**: ALWAYS include this section first. Check which pipeline files exist in `.shotgun/` (research.md, specification.md, plan.md, tasks.md) and list only the ones that exist. If none exist, omit this section.
|
|
127
128
|
- **Project Overview**: Brief description and key technologies from specification.md
|
|
128
129
|
- **Dev Environment Setup**: Installation, dependencies, dev server commands
|
|
129
130
|
- **Code Style Guidelines**: Coding conventions and patterns from research.md
|
|
@@ -170,6 +171,14 @@ For additional specialized exports (only if specifically requested):
|
|
|
170
171
|
<CORRECT_CONTENT_TEMPLATE>
|
|
171
172
|
# Agents.md - [Project Name]
|
|
172
173
|
|
|
174
|
+
## Research, Specifications, and Planning
|
|
175
|
+
|
|
176
|
+
The `.shotgun/` folder contains background research, specifications, and implementation planning files. Refer to these files for additional context:
|
|
177
|
+
- `research.md` - Codebase analysis and research findings
|
|
178
|
+
- `specification.md` - Project requirements and specifications
|
|
179
|
+
- `plan.md` - Development plan and implementation approach
|
|
180
|
+
- `tasks.md` - Task breakdown and implementation progress
|
|
181
|
+
|
|
173
182
|
## Project Overview
|
|
174
183
|
- Brief description of what the project does
|
|
175
184
|
- Key technologies and frameworks used
|
|
@@ -253,6 +262,14 @@ This project is about [making assumptions without reading files]...
|
|
|
253
262
|
<GOOD_CONTENT_EXAMPLE>
|
|
254
263
|
# Agents.md - E-commerce API Project
|
|
255
264
|
|
|
265
|
+
## Research, Specifications, and Planning
|
|
266
|
+
|
|
267
|
+
The `.shotgun/` folder contains background research, specifications, and implementation planning files. Refer to these files for additional context:
|
|
268
|
+
- `research.md` - Codebase analysis and research findings
|
|
269
|
+
- `specification.md` - Project requirements and specifications
|
|
270
|
+
- `plan.md` - Development plan and implementation approach
|
|
271
|
+
- `tasks.md` - Task breakdown and implementation progress
|
|
272
|
+
|
|
256
273
|
## Project Overview
|
|
257
274
|
- REST API for product catalog management with authentication
|
|
258
275
|
- Built with Python/FastAPI for high performance async operations
|
|
@@ -316,7 +333,7 @@ This project is about [making assumptions without reading files]...
|
|
|
316
333
|
USER INTERACTION - CLARIFY EXPORT REQUIREMENTS:
|
|
317
334
|
|
|
318
335
|
- ALWAYS ask clarifying questions when export requirements are unclear
|
|
319
|
-
- Use
|
|
336
|
+
- Use clarifying questions to gather specific details about:
|
|
320
337
|
- Target format and file type preferences
|
|
321
338
|
- Intended use case and audience for the export
|
|
322
339
|
- Specific content sections to include/exclude from files
|
|
@@ -7,7 +7,10 @@ Your extensive expertise spans, among other things:
|
|
|
7
7
|
## KEY RULES
|
|
8
8
|
|
|
9
9
|
{% if interactive_mode %}
|
|
10
|
-
0. Always ask CLARIFYING QUESTIONS if the user's request is ambiguous or lacks sufficient detail.
|
|
10
|
+
0. Always ask CLARIFYING QUESTIONS using structured output if the user's request is ambiguous or lacks sufficient detail.
|
|
11
|
+
- Return your response with the clarifying_questions field populated
|
|
12
|
+
- Do not make assumptions about what the user wants
|
|
13
|
+
- Questions should be clear, specific, and answerable
|
|
11
14
|
{% endif %}
|
|
12
15
|
1. Above all, prefer using tools to do the work and NEVER respond with text.
|
|
13
16
|
2. IMPORTANT: Always ask for review and go ahead to move forward after using write_file().
|
|
@@ -20,6 +23,7 @@ Your extensive expertise spans, among other things:
|
|
|
20
23
|
9. **Be Creative**: If the user seems not to know something, always be creative and come up with ideas that fit their thinking.
|
|
21
24
|
10. Greet the user when you're just starting to work.
|
|
22
25
|
11. DO NOT repeat yourself.
|
|
26
|
+
12. If a user has agreed to a plan, you DO NOT NEED TO FOLLOW UP with them after every step to ask "is this search query ok?".
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
## Quality Standards
|
|
@@ -6,20 +6,37 @@
|
|
|
6
6
|
{% if interactive_mode -%}
|
|
7
7
|
IMPORTANT: USER INTERACTION IS ENABLED (interactive mode).
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
## Structured Output Format
|
|
10
|
+
|
|
11
|
+
You must return responses using this structured format:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"response": "Your main response text here",
|
|
16
|
+
"clarifying_questions": ["Question 1?", "Question 2?"] // Optional, only when needed
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## When to Use Clarifying Questions
|
|
21
|
+
|
|
22
|
+
- BEFORE GETTING TO WORK: If the user's request is ambiguous, use clarifying_questions to ask what they want
|
|
23
|
+
- DURING WORK: After using write_file(), you can suggest that the user review it and ask any clarifying questions with clarifying_questions
|
|
12
24
|
- Don't assume - ask for confirmation of your understanding
|
|
13
|
-
- When in doubt about any aspect of the goal,
|
|
25
|
+
- When in doubt about any aspect of the goal, include clarifying_questions
|
|
26
|
+
|
|
27
|
+
## Important Notes
|
|
28
|
+
|
|
29
|
+
- If you don't need to ask questions, set clarifying_questions to null or omit it
|
|
30
|
+
- Keep response field concise - a paragraph at most for user communication
|
|
31
|
+
- Questions should be clear, specific, and independently answerable
|
|
32
|
+
- Don't ask multiple questions in one string - use separate array items
|
|
14
33
|
|
|
15
34
|
{% else -%}
|
|
16
35
|
|
|
17
36
|
IMPORTANT: USER INTERACTION IS DISABLED (non-interactive mode).
|
|
18
|
-
- You cannot ask clarifying questions
|
|
37
|
+
- You cannot ask clarifying questions (clarifying_questions will be ignored)
|
|
19
38
|
- Make reasonable assumptions based on best practices
|
|
20
39
|
- Use sensible defaults when information is missing
|
|
21
|
-
- Make reasonable assumptions based on industry best practices
|
|
22
|
-
- Use sensible defaults when specific details are not provided
|
|
23
40
|
- When in doubt, make reasonable assumptions and proceed with best practices
|
|
24
41
|
{% endif %}
|
|
25
42
|
|
shotgun/prompts/agents/plan.j2
CHANGED
|
@@ -118,7 +118,7 @@ USER INTERACTION - REDUCE UNCERTAINTY:
|
|
|
118
118
|
- FIRST read `research.md` and `specification.md` before asking ANY questions
|
|
119
119
|
- ONLY ask clarifying questions AFTER reading the context files
|
|
120
120
|
- Questions should be about gaps not covered in research/specification
|
|
121
|
-
- Use
|
|
121
|
+
- Use clarifying questions to gather specific details about:
|
|
122
122
|
- Information not found in the context files
|
|
123
123
|
- Clarifications on ambiguous specifications
|
|
124
124
|
- Priorities when multiple options exist
|
|
@@ -39,7 +39,7 @@ For research tasks:
|
|
|
39
39
|
## RESEARCH PRINCIPLES
|
|
40
40
|
|
|
41
41
|
{% if interactive_mode -%}
|
|
42
|
-
- CRITICAL: BEFORE RUNNING ANY SEARCH TOOL, ASK THE USER FOR APPROVAL
|
|
42
|
+
- CRITICAL: BEFORE RUNNING ANY SEARCH TOOL, ASK THE USER FOR APPROVAL using clarifying questions. Include what you plan to search for and ask if they want you to proceed.
|
|
43
43
|
{% endif -%}
|
|
44
44
|
- Build upon existing research rather than starting from scratch
|
|
45
45
|
- Focus on practical, actionable information over theoretical concepts
|
shotgun/prompts/agents/tasks.j2
CHANGED
|
@@ -99,7 +99,7 @@ Then organize tasks into logical sections:
|
|
|
99
99
|
USER INTERACTION - ASK CLARIFYING QUESTIONS:
|
|
100
100
|
|
|
101
101
|
- ALWAYS ask clarifying questions when the request is vague or ambiguous
|
|
102
|
-
- Use
|
|
102
|
+
- Use clarifying questions to gather specific details about:
|
|
103
103
|
- Specific features or functionality to prioritize
|
|
104
104
|
- Technical constraints or preferences
|
|
105
105
|
- Timeline and resource constraints
|
shotgun/telemetry.py
CHANGED
|
@@ -65,6 +65,14 @@ def setup_logfire_observability() -> bool:
|
|
|
65
65
|
# Instrument Pydantic AI for better observability
|
|
66
66
|
logfire.instrument_pydantic_ai()
|
|
67
67
|
|
|
68
|
+
# Add LogfireLoggingHandler to root logger so logfire logs also go to file
|
|
69
|
+
import logging
|
|
70
|
+
|
|
71
|
+
root_logger = logging.getLogger()
|
|
72
|
+
logfire_handler = logfire.LogfireLoggingHandler()
|
|
73
|
+
root_logger.addHandler(logfire_handler)
|
|
74
|
+
logger.debug("Added LogfireLoggingHandler to root logger for file integration")
|
|
75
|
+
|
|
68
76
|
# Set user context using baggage for all logs and spans
|
|
69
77
|
try:
|
|
70
78
|
from opentelemetry import baggage, context
|
shotgun/tui/screens/chat.py
CHANGED
|
@@ -20,10 +20,11 @@ from textual.keys import Keys
|
|
|
20
20
|
from textual.reactive import reactive
|
|
21
21
|
from textual.screen import ModalScreen, Screen
|
|
22
22
|
from textual.widget import Widget
|
|
23
|
-
from textual.widgets import Button, Label,
|
|
23
|
+
from textual.widgets import Button, Label, Static
|
|
24
24
|
|
|
25
25
|
from shotgun.agents.agent_manager import (
|
|
26
26
|
AgentManager,
|
|
27
|
+
ClarifyingQuestionsMessage,
|
|
27
28
|
MessageHistoryUpdated,
|
|
28
29
|
PartialResponseMessage,
|
|
29
30
|
)
|
|
@@ -37,7 +38,6 @@ from shotgun.agents.models import (
|
|
|
37
38
|
AgentDeps,
|
|
38
39
|
AgentType,
|
|
39
40
|
FileOperationTracker,
|
|
40
|
-
UserQuestion,
|
|
41
41
|
)
|
|
42
42
|
from shotgun.codebase.core.manager import CodebaseAlreadyIndexedError
|
|
43
43
|
from shotgun.codebase.models import IndexProgress, ProgressPhase
|
|
@@ -113,10 +113,31 @@ class StatusBar(Widget):
|
|
|
113
113
|
self.working = working
|
|
114
114
|
|
|
115
115
|
def render(self) -> str:
|
|
116
|
+
# Check if in Q&A mode first (highest priority)
|
|
117
|
+
try:
|
|
118
|
+
chat_screen = self.screen
|
|
119
|
+
if isinstance(chat_screen, ChatScreen) and chat_screen.qa_mode:
|
|
120
|
+
return (
|
|
121
|
+
"[$foreground-muted][bold $text]esc[/] to exit Q&A mode • "
|
|
122
|
+
"[bold $text]enter[/] to send answer • [bold $text]ctrl+j[/] for newline[/]"
|
|
123
|
+
)
|
|
124
|
+
except Exception: # noqa: S110
|
|
125
|
+
# If we can't access chat screen, continue with normal display
|
|
126
|
+
pass
|
|
127
|
+
|
|
116
128
|
if self.working:
|
|
117
|
-
return
|
|
129
|
+
return (
|
|
130
|
+
"[$foreground-muted][bold $text]esc[/] to stop • "
|
|
131
|
+
"[bold $text]enter[/] to send • [bold $text]ctrl+j[/] for newline • "
|
|
132
|
+
"[bold $text]ctrl+p[/] command palette • [bold $text]shift+tab[/] cycle modes • "
|
|
133
|
+
"/help for commands[/]"
|
|
134
|
+
)
|
|
118
135
|
else:
|
|
119
|
-
return
|
|
136
|
+
return (
|
|
137
|
+
"[$foreground-muted][bold $text]enter[/] to send • "
|
|
138
|
+
"[bold $text]ctrl+j[/] for newline • [bold $text]ctrl+p[/] command palette • "
|
|
139
|
+
"[bold $text]shift+tab[/] cycle modes • /help for commands[/]"
|
|
140
|
+
)
|
|
120
141
|
|
|
121
142
|
|
|
122
143
|
class ModeIndicator(Widget):
|
|
@@ -141,6 +162,18 @@ class ModeIndicator(Widget):
|
|
|
141
162
|
|
|
142
163
|
def render(self) -> str:
|
|
143
164
|
"""Render the mode indicator."""
|
|
165
|
+
# Check if in Q&A mode first
|
|
166
|
+
try:
|
|
167
|
+
chat_screen = self.screen
|
|
168
|
+
if isinstance(chat_screen, ChatScreen) and chat_screen.qa_mode:
|
|
169
|
+
return (
|
|
170
|
+
"[bold $text-accent]Q&A mode[/]"
|
|
171
|
+
"[$foreground-muted] (Answer the clarifying questions or ESC to cancel)[/]"
|
|
172
|
+
)
|
|
173
|
+
except Exception: # noqa: S110
|
|
174
|
+
# If we can't access chat screen, continue with normal display
|
|
175
|
+
pass
|
|
176
|
+
|
|
144
177
|
mode_display = {
|
|
145
178
|
AgentType.RESEARCH: "Research",
|
|
146
179
|
AgentType.PLAN: "Planning",
|
|
@@ -149,10 +182,16 @@ class ModeIndicator(Widget):
|
|
|
149
182
|
AgentType.EXPORT: "Export",
|
|
150
183
|
}
|
|
151
184
|
mode_description = {
|
|
152
|
-
AgentType.RESEARCH:
|
|
185
|
+
AgentType.RESEARCH: (
|
|
186
|
+
"Research topics with web search and synthesize findings"
|
|
187
|
+
),
|
|
153
188
|
AgentType.PLAN: "Create comprehensive, actionable plans with milestones",
|
|
154
|
-
AgentType.TASKS:
|
|
155
|
-
|
|
189
|
+
AgentType.TASKS: (
|
|
190
|
+
"Generate specific, actionable tasks from research and plans"
|
|
191
|
+
),
|
|
192
|
+
AgentType.SPECIFY: (
|
|
193
|
+
"Create detailed specifications and requirements documents"
|
|
194
|
+
),
|
|
156
195
|
AgentType.EXPORT: "Export artifacts and findings to various formats",
|
|
157
196
|
}
|
|
158
197
|
|
|
@@ -163,7 +202,10 @@ class ModeIndicator(Widget):
|
|
|
163
202
|
has_content = self.progress_checker.has_mode_content(self.mode)
|
|
164
203
|
status_icon = " ✓" if has_content else ""
|
|
165
204
|
|
|
166
|
-
return
|
|
205
|
+
return (
|
|
206
|
+
f"[bold $text-accent]{mode_title}{status_icon} mode[/]"
|
|
207
|
+
f"[$foreground-muted] ({description})[/]"
|
|
208
|
+
)
|
|
167
209
|
|
|
168
210
|
|
|
169
211
|
class CodebaseIndexPromptScreen(ModalScreen[bool]):
|
|
@@ -199,7 +241,8 @@ class CodebaseIndexPromptScreen(ModalScreen[bool]):
|
|
|
199
241
|
yield Static(
|
|
200
242
|
f"Would you like to index the codebase at:\n{Path.cwd()}\n\n"
|
|
201
243
|
"This is required for the agent to understand your code and answer "
|
|
202
|
-
"questions about it. Without indexing, the agent cannot analyze
|
|
244
|
+
"questions about it. Without indexing, the agent cannot analyze "
|
|
245
|
+
"your codebase."
|
|
203
246
|
)
|
|
204
247
|
with Container(id="index-prompt-buttons"):
|
|
205
248
|
yield Button(
|
|
@@ -238,11 +281,16 @@ class ChatScreen(Screen[None]):
|
|
|
238
281
|
history: PromptHistory = PromptHistory()
|
|
239
282
|
messages = reactive(list[ModelMessage | HintMessage]())
|
|
240
283
|
working = reactive(False)
|
|
241
|
-
question: reactive[UserQuestion | None] = reactive(None)
|
|
242
284
|
indexing_job: reactive[CodebaseIndexSelection | None] = reactive(None)
|
|
243
285
|
partial_message: reactive[ModelMessage | None] = reactive(None)
|
|
244
286
|
_current_worker = None # Track the current running worker for cancellation
|
|
245
287
|
|
|
288
|
+
# Q&A mode state (for structured output clarifying questions)
|
|
289
|
+
qa_mode = reactive(False)
|
|
290
|
+
qa_questions: list[str] = []
|
|
291
|
+
qa_current_index = reactive(0)
|
|
292
|
+
qa_answers: list[str] = []
|
|
293
|
+
|
|
246
294
|
def __init__(self, continue_session: bool = False) -> None:
|
|
247
295
|
super().__init__()
|
|
248
296
|
# Get the model configuration and services
|
|
@@ -282,11 +330,19 @@ class ChatScreen(Screen[None]):
|
|
|
282
330
|
self._load_conversation()
|
|
283
331
|
|
|
284
332
|
self.call_later(self.check_if_codebase_is_indexed)
|
|
285
|
-
# Start the question listener worker to handle ask_user interactions
|
|
286
|
-
self.call_later(self.add_question_listener)
|
|
287
333
|
|
|
288
334
|
async def on_key(self, event: events.Key) -> None:
|
|
289
335
|
"""Handle key presses for cancellation."""
|
|
336
|
+
# If escape is pressed during Q&A mode, exit Q&A
|
|
337
|
+
if event.key in (Keys.Escape, Keys.ControlC) and self.qa_mode:
|
|
338
|
+
self._exit_qa_mode()
|
|
339
|
+
# Re-enable the input
|
|
340
|
+
prompt_input = self.query_one(PromptInput)
|
|
341
|
+
prompt_input.focus()
|
|
342
|
+
# Prevent the event from propagating (don't quit the app)
|
|
343
|
+
event.stop()
|
|
344
|
+
return
|
|
345
|
+
|
|
290
346
|
# If escape or ctrl+c is pressed while agent is working, cancel the operation
|
|
291
347
|
if (
|
|
292
348
|
event.key in (Keys.Escape, Keys.ControlC)
|
|
@@ -372,24 +428,33 @@ class ChatScreen(Screen[None]):
|
|
|
372
428
|
status_bar.working = is_working
|
|
373
429
|
status_bar.refresh()
|
|
374
430
|
|
|
431
|
+
def watch_qa_mode(self, qa_mode_active: bool) -> None:
|
|
432
|
+
"""Update UI when Q&A mode state changes."""
|
|
433
|
+
if self.is_mounted:
|
|
434
|
+
# Update status bar to show "ESC to exit Q&A mode"
|
|
435
|
+
status_bar = self.query_one(StatusBar)
|
|
436
|
+
status_bar.refresh()
|
|
437
|
+
|
|
438
|
+
# Update mode indicator to show "Q&A mode"
|
|
439
|
+
mode_indicator = self.query_one(ModeIndicator)
|
|
440
|
+
mode_indicator.refresh()
|
|
441
|
+
|
|
375
442
|
def watch_messages(self, messages: list[ModelMessage | HintMessage]) -> None:
|
|
376
443
|
"""Update the chat history when messages change."""
|
|
377
444
|
if self.is_mounted:
|
|
378
445
|
chat_history = self.query_one(ChatHistory)
|
|
379
446
|
chat_history.update_messages(messages)
|
|
380
447
|
|
|
381
|
-
def watch_question(self, question: UserQuestion | None) -> None:
|
|
382
|
-
"""Update the question display."""
|
|
383
|
-
if self.is_mounted:
|
|
384
|
-
question_display = self.query_one("#question-display", Markdown)
|
|
385
|
-
if question:
|
|
386
|
-
question_display.update(f"Question:\n\n{question.question}")
|
|
387
|
-
question_display.display = True
|
|
388
|
-
else:
|
|
389
|
-
question_display.update("")
|
|
390
|
-
question_display.display = False
|
|
391
|
-
|
|
392
448
|
def action_toggle_mode(self) -> None:
|
|
449
|
+
# Prevent mode switching during Q&A
|
|
450
|
+
if self.qa_mode:
|
|
451
|
+
self.notify(
|
|
452
|
+
"Cannot switch modes while answering questions",
|
|
453
|
+
severity="warning",
|
|
454
|
+
timeout=3,
|
|
455
|
+
)
|
|
456
|
+
return
|
|
457
|
+
|
|
393
458
|
modes = [
|
|
394
459
|
AgentType.RESEARCH,
|
|
395
460
|
AgentType.SPECIFY,
|
|
@@ -410,20 +475,11 @@ class ChatScreen(Screen[None]):
|
|
|
410
475
|
else:
|
|
411
476
|
self.notify("No usage hint available", severity="error")
|
|
412
477
|
|
|
413
|
-
@work
|
|
414
|
-
async def add_question_listener(self) -> None:
|
|
415
|
-
while True:
|
|
416
|
-
question = await self.deps.queue.get()
|
|
417
|
-
self.question = question
|
|
418
|
-
await question.result
|
|
419
|
-
self.deps.queue.task_done()
|
|
420
|
-
|
|
421
478
|
def compose(self) -> ComposeResult:
|
|
422
479
|
"""Create child widgets for the app."""
|
|
423
480
|
with Container(id="window"):
|
|
424
481
|
yield self.agent_manager
|
|
425
482
|
yield ChatHistory()
|
|
426
|
-
yield Markdown(markdown="", id="question-display")
|
|
427
483
|
with Container(id="footer"):
|
|
428
484
|
yield Spinner(
|
|
429
485
|
text="Processing...",
|
|
@@ -464,6 +520,42 @@ class ChatScreen(Screen[None]):
|
|
|
464
520
|
partial_response_widget = self.query_one(ChatHistory)
|
|
465
521
|
partial_response_widget.partial_response = None
|
|
466
522
|
|
|
523
|
+
def _exit_qa_mode(self) -> None:
|
|
524
|
+
"""Exit Q&A mode and clean up state."""
|
|
525
|
+
# Track cancellation event
|
|
526
|
+
track_event(
|
|
527
|
+
"qa_mode_cancelled",
|
|
528
|
+
{
|
|
529
|
+
"questions_total": len(self.qa_questions),
|
|
530
|
+
"questions_answered": len(self.qa_answers),
|
|
531
|
+
},
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Clear Q&A state
|
|
535
|
+
self.qa_mode = False
|
|
536
|
+
self.qa_questions = []
|
|
537
|
+
self.qa_answers = []
|
|
538
|
+
self.qa_current_index = 0
|
|
539
|
+
|
|
540
|
+
# Show cancellation message
|
|
541
|
+
self.mount_hint("⚠️ Q&A cancelled - You can continue the conversation.")
|
|
542
|
+
|
|
543
|
+
@on(ClarifyingQuestionsMessage)
|
|
544
|
+
def handle_clarifying_questions(self, event: ClarifyingQuestionsMessage) -> None:
|
|
545
|
+
"""Handle clarifying questions from agent structured output.
|
|
546
|
+
|
|
547
|
+
Note: Hints are now added synchronously in agent_manager.run() before this
|
|
548
|
+
handler is called, so we only need to set up Q&A mode state here.
|
|
549
|
+
"""
|
|
550
|
+
# Clear any streaming partial response (removes final_result JSON)
|
|
551
|
+
self._clear_partial_response()
|
|
552
|
+
|
|
553
|
+
# Enter Q&A mode
|
|
554
|
+
self.qa_mode = True
|
|
555
|
+
self.qa_questions = event.questions
|
|
556
|
+
self.qa_current_index = 0
|
|
557
|
+
self.qa_answers = []
|
|
558
|
+
|
|
467
559
|
@on(MessageHistoryUpdated)
|
|
468
560
|
def handle_message_history_updated(self, event: MessageHistoryUpdated) -> None:
|
|
469
561
|
"""Handle message history updates from the agent manager."""
|
|
@@ -519,6 +611,61 @@ class ChatScreen(Screen[None]):
|
|
|
519
611
|
self.value = ""
|
|
520
612
|
return
|
|
521
613
|
|
|
614
|
+
# Handle Q&A mode (from structured output clarifying questions)
|
|
615
|
+
if self.qa_mode and self.qa_questions:
|
|
616
|
+
# Collect answer
|
|
617
|
+
self.qa_answers.append(text)
|
|
618
|
+
|
|
619
|
+
# Show answer
|
|
620
|
+
if len(self.qa_questions) == 1:
|
|
621
|
+
self.agent_manager.add_hint_message(
|
|
622
|
+
HintMessage(message=f"**A:** {text}")
|
|
623
|
+
)
|
|
624
|
+
else:
|
|
625
|
+
q_num = self.qa_current_index + 1
|
|
626
|
+
self.agent_manager.add_hint_message(
|
|
627
|
+
HintMessage(message=f"**A{q_num}:** {text}")
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
# Move to next or finish
|
|
631
|
+
self.qa_current_index += 1
|
|
632
|
+
|
|
633
|
+
if self.qa_current_index < len(self.qa_questions):
|
|
634
|
+
# Show next question
|
|
635
|
+
next_q = self.qa_questions[self.qa_current_index]
|
|
636
|
+
next_q_num = self.qa_current_index + 1
|
|
637
|
+
self.agent_manager.add_hint_message(
|
|
638
|
+
HintMessage(message=f"**Q{next_q_num}:** {next_q}")
|
|
639
|
+
)
|
|
640
|
+
else:
|
|
641
|
+
# All answered - format and send back
|
|
642
|
+
if len(self.qa_questions) == 1:
|
|
643
|
+
# Single question - just send the answer
|
|
644
|
+
formatted_qa = f"Q: {self.qa_questions[0]}\nA: {self.qa_answers[0]}"
|
|
645
|
+
else:
|
|
646
|
+
# Multiple questions - format all Q&A pairs
|
|
647
|
+
formatted_qa = "\n\n".join(
|
|
648
|
+
f"Q{i + 1}: {q}\nA{i + 1}: {a}"
|
|
649
|
+
for i, (q, a) in enumerate(
|
|
650
|
+
zip(self.qa_questions, self.qa_answers, strict=True)
|
|
651
|
+
)
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
# Exit Q&A mode
|
|
655
|
+
self.qa_mode = False
|
|
656
|
+
self.qa_questions = []
|
|
657
|
+
self.qa_answers = []
|
|
658
|
+
self.qa_current_index = 0
|
|
659
|
+
|
|
660
|
+
# Send answers back to agent
|
|
661
|
+
self.run_agent(formatted_qa)
|
|
662
|
+
|
|
663
|
+
# Clear input
|
|
664
|
+
prompt_input = self.query_one(PromptInput)
|
|
665
|
+
prompt_input.clear()
|
|
666
|
+
self.value = ""
|
|
667
|
+
return
|
|
668
|
+
|
|
522
669
|
# Check if it's a command
|
|
523
670
|
if self.command_handler.is_command(text):
|
|
524
671
|
success, response = self.command_handler.handle_command(text)
|
|
@@ -785,7 +932,9 @@ class ChatScreen(Screen[None]):
|
|
|
785
932
|
|
|
786
933
|
def help_text_with_codebase(already_indexed: bool = False) -> str:
|
|
787
934
|
return (
|
|
788
|
-
"Howdy! Welcome to Shotgun - the context tool for software engineering. \n\
|
|
935
|
+
"Howdy! Welcome to Shotgun - the context tool for software engineering. \n\n"
|
|
936
|
+
"You can research, build specs, plan, create tasks, and export context to your "
|
|
937
|
+
"favorite code-gen agents.\n\n"
|
|
789
938
|
f"{'' if already_indexed else 'Once your codebase is indexed, '}I can help with:\n\n"
|
|
790
939
|
"- Speccing out a new feature\n"
|
|
791
940
|
"- Onboarding you onto this project\n"
|
|
@@ -796,7 +945,9 @@ def help_text_with_codebase(already_indexed: bool = False) -> str:
|
|
|
796
945
|
|
|
797
946
|
def help_text_empty_dir() -> str:
|
|
798
947
|
return (
|
|
799
|
-
"Howdy! Welcome to Shotgun - the context tool for software engineering.\n\
|
|
948
|
+
"Howdy! Welcome to Shotgun - the context tool for software engineering.\n\n"
|
|
949
|
+
"You can research, build specs, plan, create tasks, and export context to your "
|
|
950
|
+
"favorite code-gen agents.\n\n"
|
|
800
951
|
"What would you like to build? Here are some examples:\n\n"
|
|
801
952
|
"- Research FastAPI vs Django\n"
|
|
802
953
|
"- Plan my new web app using React\n"
|