shotgun-sh 0.1.0.dev22__py3-none-any.whl → 0.1.0.dev23__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 +95 -15
- shotgun/agents/common.py +139 -24
- shotgun/agents/conversation_history.py +56 -0
- shotgun/agents/conversation_manager.py +105 -0
- shotgun/agents/export.py +5 -2
- shotgun/agents/models.py +16 -7
- shotgun/agents/plan.py +2 -1
- shotgun/agents/research.py +2 -1
- shotgun/agents/specify.py +2 -1
- shotgun/agents/tasks.py +5 -2
- shotgun/agents/tools/file_management.py +67 -2
- shotgun/main.py +9 -1
- shotgun/prompts/agents/export.j2 +14 -11
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +6 -9
- shotgun/prompts/agents/plan.j2 +9 -13
- shotgun/prompts/agents/research.j2 +11 -14
- shotgun/prompts/agents/specify.j2 +9 -12
- shotgun/prompts/agents/state/system_state.j2 +27 -5
- shotgun/prompts/agents/tasks.j2 +12 -12
- shotgun/sdk/services.py +0 -14
- shotgun/tui/app.py +9 -4
- shotgun/tui/screens/chat.py +57 -8
- shotgun/tui/screens/chat_screen/command_providers.py +1 -1
- shotgun/tui/screens/chat_screen/history.py +6 -0
- shotgun/tui/utils/mode_progress.py +111 -78
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/METADATA +8 -9
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/RECORD +30 -49
- shotgun/agents/artifact_state.py +0 -58
- shotgun/agents/tools/artifact_management.py +0 -481
- shotgun/artifacts/__init__.py +0 -17
- shotgun/artifacts/exceptions.py +0 -89
- shotgun/artifacts/manager.py +0 -530
- shotgun/artifacts/models.py +0 -334
- shotgun/artifacts/service.py +0 -463
- shotgun/artifacts/templates/__init__.py +0 -10
- shotgun/artifacts/templates/loader.py +0 -252
- shotgun/artifacts/templates/models.py +0 -136
- shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +0 -66
- shotgun/artifacts/templates/research/market_research.yaml +0 -585
- shotgun/artifacts/templates/research/sdk_comparison.yaml +0 -257
- shotgun/artifacts/templates/specify/prd.yaml +0 -331
- shotgun/artifacts/templates/specify/product_spec.yaml +0 -301
- shotgun/artifacts/utils.py +0 -76
- shotgun/prompts/agents/partials/artifact_system.j2 +0 -32
- shotgun/prompts/agents/state/artifact_templates_available.j2 +0 -20
- shotgun/prompts/agents/state/existing_artifacts_available.j2 +0 -25
- shotgun/sdk/artifact_models.py +0 -186
- shotgun/sdk/artifacts.py +0 -448
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.1.0.dev22.dist-info → shotgun_sh-0.1.0.dev23.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
"""Artifact management tools for Pydantic AI agents.
|
|
2
|
-
|
|
3
|
-
These tools provide agents with the ability to create and manage structured
|
|
4
|
-
artifacts instead of flat markdown files.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from pydantic_ai import RunContext
|
|
8
|
-
|
|
9
|
-
from shotgun.agents.models import AgentDeps
|
|
10
|
-
from shotgun.artifacts.utils import handle_agent_mode_parsing
|
|
11
|
-
from shotgun.logging_config import setup_logger
|
|
12
|
-
|
|
13
|
-
logger = setup_logger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async def create_artifact(
|
|
17
|
-
ctx: RunContext[AgentDeps],
|
|
18
|
-
artifact_id: str,
|
|
19
|
-
agent_mode: str,
|
|
20
|
-
name: str,
|
|
21
|
-
template_id: str = "",
|
|
22
|
-
) -> str:
|
|
23
|
-
"""Create a new artifact.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
ctx: RunContext containing AgentDeps with artifact service
|
|
27
|
-
artifact_id: Unique identifier for the artifact (slug format)
|
|
28
|
-
agent_mode: Agent mode (research, plan, tasks)
|
|
29
|
-
name: Human-readable name for the artifact
|
|
30
|
-
template_id: Optional template ID to use for creating the artifact
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Success message including template content if template was used, or error message
|
|
34
|
-
|
|
35
|
-
Example:
|
|
36
|
-
create_artifact("market-analysis", "research", "Market Analysis")
|
|
37
|
-
create_artifact("market-study", "research", "Market Study", "research/market_research")
|
|
38
|
-
"""
|
|
39
|
-
logger.debug("🔧 Creating artifact: %s/%s", agent_mode, artifact_id)
|
|
40
|
-
|
|
41
|
-
# Parse and validate agent mode
|
|
42
|
-
mode, error_msg = handle_agent_mode_parsing(agent_mode)
|
|
43
|
-
if error_msg:
|
|
44
|
-
logger.error("❌ Create artifact failed: %s", error_msg)
|
|
45
|
-
return f"Error: {error_msg}"
|
|
46
|
-
# Type checker hint: mode is validated above
|
|
47
|
-
if mode is None:
|
|
48
|
-
return "Error: Invalid agent mode"
|
|
49
|
-
|
|
50
|
-
try:
|
|
51
|
-
service = ctx.deps.artifact_service
|
|
52
|
-
|
|
53
|
-
# Pass template_id if provided and not empty
|
|
54
|
-
template_to_use = template_id.strip() if template_id.strip() else None
|
|
55
|
-
artifact = service.create_artifact(artifact_id, mode, name, template_to_use)
|
|
56
|
-
|
|
57
|
-
# Track the artifact file creation
|
|
58
|
-
from shotgun.agents.models import FileOperationType
|
|
59
|
-
from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
60
|
-
|
|
61
|
-
artifact_path = (
|
|
62
|
-
get_shotgun_base_path() / mode.value / artifact_id / "artifact.yaml"
|
|
63
|
-
)
|
|
64
|
-
ctx.deps.file_tracker.add_operation(artifact_path, FileOperationType.CREATED)
|
|
65
|
-
|
|
66
|
-
success_msg = (
|
|
67
|
-
f"Created artifact '{artifact_id}' in {agent_mode} mode with name '{name}'"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# If template was used, include template content in the response
|
|
71
|
-
if artifact.has_template():
|
|
72
|
-
template_content = artifact.load_template_from_file()
|
|
73
|
-
if template_content:
|
|
74
|
-
success_msg += f"\n\nUsing template: {template_content.get('name', artifact.get_template_id())}"
|
|
75
|
-
|
|
76
|
-
if "purpose" in template_content:
|
|
77
|
-
success_msg += f"\nPurpose: {template_content['purpose']}"
|
|
78
|
-
if "prompt" in template_content:
|
|
79
|
-
success_msg += f"\nPrompt: {template_content['prompt']}"
|
|
80
|
-
|
|
81
|
-
if "sections" in template_content and isinstance(
|
|
82
|
-
template_content["sections"], dict
|
|
83
|
-
):
|
|
84
|
-
success_msg += "\nSections to complete:"
|
|
85
|
-
|
|
86
|
-
# Sort sections by order if available
|
|
87
|
-
sections_dict = template_content["sections"]
|
|
88
|
-
sorted_sections = sorted(
|
|
89
|
-
sections_dict.items(),
|
|
90
|
-
key=lambda x: x[1].get("order", 999)
|
|
91
|
-
if isinstance(x[1], dict)
|
|
92
|
-
else 999,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
for section_key, section_info in sorted_sections:
|
|
96
|
-
if isinstance(section_info, dict):
|
|
97
|
-
instructions = section_info.get("instructions", "")
|
|
98
|
-
success_msg += f"\n- {section_key}: {instructions}"
|
|
99
|
-
if section_info.get("depends_on"):
|
|
100
|
-
depends_on = section_info["depends_on"]
|
|
101
|
-
if isinstance(depends_on, list):
|
|
102
|
-
success_msg += (
|
|
103
|
-
f" (depends on: {', '.join(depends_on)})"
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
logger.debug("✅ %s", success_msg)
|
|
107
|
-
return success_msg
|
|
108
|
-
|
|
109
|
-
except Exception as e:
|
|
110
|
-
error_msg = f"Failed to create artifact '{artifact_id}': {str(e)}"
|
|
111
|
-
logger.error("❌ Create artifact failed: %s", error_msg)
|
|
112
|
-
return f"Error: {error_msg}"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
async def read_artifact(
|
|
116
|
-
ctx: RunContext[AgentDeps],
|
|
117
|
-
artifact_id: str,
|
|
118
|
-
agent_mode: str,
|
|
119
|
-
) -> str:
|
|
120
|
-
"""Read all sections of an artifact.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
ctx: RunContext containing AgentDeps with artifact service
|
|
124
|
-
artifact_id: Artifact identifier
|
|
125
|
-
agent_mode: Agent mode (research, plan, tasks)
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Combined content of all sections or error message
|
|
129
|
-
|
|
130
|
-
Example:
|
|
131
|
-
read_artifact("market-analysis", "research")
|
|
132
|
-
"""
|
|
133
|
-
logger.debug("🔧 Reading artifact: %s/%s", agent_mode, artifact_id)
|
|
134
|
-
|
|
135
|
-
# Parse and validate agent mode
|
|
136
|
-
mode, error_msg = handle_agent_mode_parsing(agent_mode)
|
|
137
|
-
if error_msg:
|
|
138
|
-
logger.error("❌ Read artifact failed: %s", error_msg)
|
|
139
|
-
return f"Error: {error_msg}"
|
|
140
|
-
# Type checker hint: mode is validated above
|
|
141
|
-
if mode is None:
|
|
142
|
-
return "Error: Invalid agent mode"
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
service = ctx.deps.artifact_service
|
|
146
|
-
artifact = service.get_artifact(artifact_id, mode, "")
|
|
147
|
-
|
|
148
|
-
if not artifact.sections:
|
|
149
|
-
return f"Artifact '{artifact_id}' exists but has no sections."
|
|
150
|
-
|
|
151
|
-
# Combine all sections with headers
|
|
152
|
-
content_parts = [f"# {artifact.name}\n"]
|
|
153
|
-
|
|
154
|
-
# Include template information if artifact was created from a template
|
|
155
|
-
if artifact.has_template():
|
|
156
|
-
template_content = artifact.load_template_from_file()
|
|
157
|
-
if template_content:
|
|
158
|
-
content_parts.append("\n## Template Information\n")
|
|
159
|
-
content_parts.append(f"**Template ID:** {artifact.get_template_id()}\n")
|
|
160
|
-
|
|
161
|
-
# Extract template info from the loaded template file
|
|
162
|
-
if "name" in template_content:
|
|
163
|
-
content_parts.append(f"**Template:** {template_content['name']}\n")
|
|
164
|
-
if "purpose" in template_content:
|
|
165
|
-
content_parts.append(
|
|
166
|
-
f"**Purpose:** {template_content['purpose']}\n"
|
|
167
|
-
)
|
|
168
|
-
if "prompt" in template_content:
|
|
169
|
-
content_parts.append(f"**Prompt:** {template_content['prompt']}\n")
|
|
170
|
-
|
|
171
|
-
if "sections" in template_content and isinstance(
|
|
172
|
-
template_content["sections"], dict
|
|
173
|
-
):
|
|
174
|
-
content_parts.append("\n### Template Sections:\n")
|
|
175
|
-
|
|
176
|
-
# Sort sections by order if available
|
|
177
|
-
sections_dict = template_content["sections"]
|
|
178
|
-
sorted_sections = sorted(
|
|
179
|
-
sections_dict.items(),
|
|
180
|
-
key=lambda x: x[1].get("order", 999)
|
|
181
|
-
if isinstance(x[1], dict)
|
|
182
|
-
else 999,
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
for section_key, section_info in sorted_sections:
|
|
186
|
-
if isinstance(section_info, dict):
|
|
187
|
-
content_parts.append(
|
|
188
|
-
f"- **{section_key}:** {section_info.get('instructions', '')}"
|
|
189
|
-
)
|
|
190
|
-
if section_info.get("depends_on"):
|
|
191
|
-
depends_on = section_info["depends_on"]
|
|
192
|
-
if isinstance(depends_on, list):
|
|
193
|
-
content_parts.append(
|
|
194
|
-
f" *(depends on: {', '.join(depends_on)})*"
|
|
195
|
-
)
|
|
196
|
-
content_parts.append("")
|
|
197
|
-
|
|
198
|
-
for section in artifact.get_ordered_sections():
|
|
199
|
-
content_parts.append(f"\n## {section.title}\n")
|
|
200
|
-
if section.content:
|
|
201
|
-
content_parts.append(f"{section.content}\n")
|
|
202
|
-
|
|
203
|
-
combined_content = "\n".join(content_parts)
|
|
204
|
-
logger.debug(
|
|
205
|
-
"📄 Read artifact with %d sections (%d characters)",
|
|
206
|
-
len(artifact.sections),
|
|
207
|
-
len(combined_content),
|
|
208
|
-
)
|
|
209
|
-
return combined_content
|
|
210
|
-
|
|
211
|
-
except Exception as e:
|
|
212
|
-
error_msg = f"Failed to read artifact '{artifact_id}': {str(e)}"
|
|
213
|
-
logger.error("❌ Read artifact failed: %s", error_msg)
|
|
214
|
-
return f"Error: {error_msg}"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
async def write_artifact_section(
|
|
218
|
-
ctx: RunContext[AgentDeps],
|
|
219
|
-
artifact_id: str,
|
|
220
|
-
agent_mode: str,
|
|
221
|
-
section_number: int,
|
|
222
|
-
section_slug: str,
|
|
223
|
-
section_title: str,
|
|
224
|
-
content: str,
|
|
225
|
-
) -> str:
|
|
226
|
-
"""Write content to a specific section of an artifact.
|
|
227
|
-
|
|
228
|
-
Creates the artifact and/or section if they don't exist.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
ctx: RunContext containing AgentDeps with artifact service
|
|
232
|
-
artifact_id: Artifact identifier
|
|
233
|
-
agent_mode: Agent mode (research, plan, tasks)
|
|
234
|
-
section_number: Section number (1, 2, 3, etc.)
|
|
235
|
-
section_slug: URL-friendly section identifier
|
|
236
|
-
section_title: Human-readable section title
|
|
237
|
-
content: Section content in markdown
|
|
238
|
-
|
|
239
|
-
Returns:
|
|
240
|
-
Success message or error message
|
|
241
|
-
|
|
242
|
-
Example:
|
|
243
|
-
write_artifact_section("market-analysis", "research", 1, "overview", "Market Overview", "...")
|
|
244
|
-
"""
|
|
245
|
-
logger.debug(
|
|
246
|
-
"🔧 Writing to artifact section: %s/%s section %d",
|
|
247
|
-
agent_mode,
|
|
248
|
-
artifact_id,
|
|
249
|
-
section_number,
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
# Parse and validate agent mode
|
|
253
|
-
mode, error_msg = handle_agent_mode_parsing(agent_mode)
|
|
254
|
-
if error_msg:
|
|
255
|
-
logger.error("❌ Write artifact section failed: %s", error_msg)
|
|
256
|
-
return f"Error: {error_msg}"
|
|
257
|
-
|
|
258
|
-
# At this point, mode is guaranteed to be not None due to successful validation
|
|
259
|
-
if mode is None:
|
|
260
|
-
return "Error: Agent mode validation failed"
|
|
261
|
-
|
|
262
|
-
try:
|
|
263
|
-
service = ctx.deps.artifact_service
|
|
264
|
-
|
|
265
|
-
# Get or create the section
|
|
266
|
-
section, created = service.get_or_create_section(
|
|
267
|
-
artifact_id, mode, section_number, section_slug, section_title, content
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
# Track the section file operation
|
|
271
|
-
from shotgun.agents.models import FileOperationType
|
|
272
|
-
from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
273
|
-
|
|
274
|
-
section_path = (
|
|
275
|
-
get_shotgun_base_path()
|
|
276
|
-
/ mode.value
|
|
277
|
-
/ artifact_id
|
|
278
|
-
/ f"{section_number:02d}_{section_slug}.md"
|
|
279
|
-
)
|
|
280
|
-
operation = FileOperationType.CREATED if created else FileOperationType.UPDATED
|
|
281
|
-
ctx.deps.file_tracker.add_operation(section_path, operation)
|
|
282
|
-
|
|
283
|
-
if created:
|
|
284
|
-
success_msg = (
|
|
285
|
-
f"Created section {section_number} '{section_title}' "
|
|
286
|
-
f"in artifact '{artifact_id}' with {len(content)} characters"
|
|
287
|
-
)
|
|
288
|
-
else:
|
|
289
|
-
# Update existing section content
|
|
290
|
-
service.update_section(artifact_id, mode, section_number, content=content)
|
|
291
|
-
success_msg = (
|
|
292
|
-
f"Updated section {section_number} '{section_title}' "
|
|
293
|
-
f"in artifact '{artifact_id}' with {len(content)} characters"
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
logger.debug("✅ %s", success_msg)
|
|
297
|
-
return success_msg
|
|
298
|
-
|
|
299
|
-
except Exception as e:
|
|
300
|
-
error_msg = (
|
|
301
|
-
f"Failed to write section {section_number} "
|
|
302
|
-
f"to artifact '{artifact_id}': {str(e)}"
|
|
303
|
-
)
|
|
304
|
-
logger.error("❌ Write artifact section failed: %s", error_msg)
|
|
305
|
-
return f"Error: {error_msg}"
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
async def read_artifact_section(
|
|
309
|
-
ctx: RunContext[AgentDeps],
|
|
310
|
-
artifact_id: str,
|
|
311
|
-
agent_mode: str,
|
|
312
|
-
section_number: int,
|
|
313
|
-
) -> str:
|
|
314
|
-
"""Read content from a specific section of an artifact.
|
|
315
|
-
|
|
316
|
-
Args:
|
|
317
|
-
ctx: RunContext containing AgentDeps with artifact service
|
|
318
|
-
artifact_id: Artifact identifier
|
|
319
|
-
agent_mode: Agent mode (research, plan, tasks)
|
|
320
|
-
section_number: Section number
|
|
321
|
-
|
|
322
|
-
Returns:
|
|
323
|
-
Section content or error message
|
|
324
|
-
|
|
325
|
-
Example:
|
|
326
|
-
read_artifact_section("market-analysis", "research", 1)
|
|
327
|
-
"""
|
|
328
|
-
logger.debug(
|
|
329
|
-
"🔧 Reading artifact section: %s/%s section %d",
|
|
330
|
-
agent_mode,
|
|
331
|
-
artifact_id,
|
|
332
|
-
section_number,
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
# Parse and validate agent mode
|
|
336
|
-
mode, error_msg = handle_agent_mode_parsing(agent_mode)
|
|
337
|
-
if error_msg:
|
|
338
|
-
logger.error("❌ Read artifact section failed: %s", error_msg)
|
|
339
|
-
return f"Error: {error_msg}"
|
|
340
|
-
|
|
341
|
-
# At this point, mode is guaranteed to be not None due to successful validation
|
|
342
|
-
if mode is None:
|
|
343
|
-
return "Error: Agent mode validation failed"
|
|
344
|
-
|
|
345
|
-
try:
|
|
346
|
-
service = ctx.deps.artifact_service
|
|
347
|
-
|
|
348
|
-
section = service.get_section(artifact_id, mode, section_number)
|
|
349
|
-
|
|
350
|
-
# Return section content (already contains title header from file storage)
|
|
351
|
-
logger.debug(
|
|
352
|
-
"📄 Read section %d with %d characters",
|
|
353
|
-
section_number,
|
|
354
|
-
len(section.content),
|
|
355
|
-
)
|
|
356
|
-
return section.content
|
|
357
|
-
|
|
358
|
-
except Exception as e:
|
|
359
|
-
error_msg = (
|
|
360
|
-
f"Failed to read section {section_number} "
|
|
361
|
-
f"from artifact '{artifact_id}': {str(e)}"
|
|
362
|
-
)
|
|
363
|
-
logger.error("❌ Read artifact section failed: %s", error_msg)
|
|
364
|
-
return f"Error: {error_msg}"
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
async def list_artifacts(
|
|
368
|
-
ctx: RunContext[AgentDeps],
|
|
369
|
-
agent_mode: str | None = None,
|
|
370
|
-
) -> str:
|
|
371
|
-
"""List all artifacts, optionally filtered by agent mode.
|
|
372
|
-
|
|
373
|
-
Args:
|
|
374
|
-
ctx: RunContext containing AgentDeps with artifact service
|
|
375
|
-
agent_mode: Optional agent mode filter (research, plan, tasks)
|
|
376
|
-
|
|
377
|
-
Returns:
|
|
378
|
-
Formatted list of artifacts or error message
|
|
379
|
-
|
|
380
|
-
Example:
|
|
381
|
-
list_artifacts("research")
|
|
382
|
-
list_artifacts() # List all artifacts
|
|
383
|
-
"""
|
|
384
|
-
logger.debug("🔧 Listing artifacts for mode: %s", agent_mode or "all")
|
|
385
|
-
|
|
386
|
-
try:
|
|
387
|
-
service = ctx.deps.artifact_service
|
|
388
|
-
|
|
389
|
-
mode = None
|
|
390
|
-
if agent_mode:
|
|
391
|
-
mode, error_msg = handle_agent_mode_parsing(agent_mode)
|
|
392
|
-
if error_msg:
|
|
393
|
-
logger.error("❌ List artifacts failed: %s", error_msg)
|
|
394
|
-
return f"Error: {error_msg}"
|
|
395
|
-
|
|
396
|
-
summaries = service.list_artifacts(mode)
|
|
397
|
-
|
|
398
|
-
if not summaries:
|
|
399
|
-
mode_text = f" for {agent_mode}" if agent_mode else ""
|
|
400
|
-
return f"No artifacts found{mode_text}."
|
|
401
|
-
|
|
402
|
-
# Format as table
|
|
403
|
-
lines = [
|
|
404
|
-
f"{'Agent':<10} {'ID':<25} {'Sections':<8} {'Updated'}",
|
|
405
|
-
"-" * 55,
|
|
406
|
-
]
|
|
407
|
-
|
|
408
|
-
for summary in summaries:
|
|
409
|
-
lines.append(
|
|
410
|
-
f"{summary.agent_mode.value:<10} "
|
|
411
|
-
f"{summary.artifact_id[:25]:<25} "
|
|
412
|
-
f"{summary.section_count:<8} "
|
|
413
|
-
f"{summary.updated_at.strftime('%Y-%m-%d')}"
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
if len(summaries) > 0:
|
|
417
|
-
lines.append(f"\nTotal: {len(summaries)} artifacts")
|
|
418
|
-
|
|
419
|
-
result = "\n".join(lines)
|
|
420
|
-
logger.debug("📄 Listed %d artifacts", len(summaries))
|
|
421
|
-
return result
|
|
422
|
-
|
|
423
|
-
except Exception as e:
|
|
424
|
-
error_msg = f"Failed to list artifacts: {str(e)}"
|
|
425
|
-
logger.error("❌ List artifacts failed: %s", error_msg)
|
|
426
|
-
return f"Error: {error_msg}"
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
async def list_artifact_templates(
|
|
430
|
-
ctx: RunContext[AgentDeps],
|
|
431
|
-
agent_mode: str | None = None,
|
|
432
|
-
) -> str:
|
|
433
|
-
"""List available artifact templates, optionally filtered by agent mode.
|
|
434
|
-
|
|
435
|
-
Args:
|
|
436
|
-
ctx: RunContext containing AgentDeps with artifact service
|
|
437
|
-
agent_mode: Optional agent mode filter (research, plan, tasks)
|
|
438
|
-
|
|
439
|
-
Returns:
|
|
440
|
-
Formatted list of templates or error message
|
|
441
|
-
|
|
442
|
-
Example:
|
|
443
|
-
list_artifact_templates("research")
|
|
444
|
-
list_artifact_templates() # List all templates
|
|
445
|
-
"""
|
|
446
|
-
logger.debug("🔧 Listing templates for mode: %s", agent_mode or "all")
|
|
447
|
-
|
|
448
|
-
try:
|
|
449
|
-
service = ctx.deps.artifact_service
|
|
450
|
-
|
|
451
|
-
mode = None
|
|
452
|
-
if agent_mode:
|
|
453
|
-
mode, error_msg = handle_agent_mode_parsing(agent_mode)
|
|
454
|
-
if error_msg:
|
|
455
|
-
logger.error("❌ List templates failed: %s", error_msg)
|
|
456
|
-
return f"Error: {error_msg}"
|
|
457
|
-
|
|
458
|
-
templates = service.list_templates(mode)
|
|
459
|
-
|
|
460
|
-
if not templates:
|
|
461
|
-
mode_text = f" for {agent_mode}" if agent_mode else ""
|
|
462
|
-
return f"No templates found{mode_text}."
|
|
463
|
-
|
|
464
|
-
# Format as list with template details
|
|
465
|
-
lines = ["Available Templates:"]
|
|
466
|
-
|
|
467
|
-
for template in templates:
|
|
468
|
-
lines.append(f"\n• {template.template_id}")
|
|
469
|
-
lines.append(f" Name: {template.name}")
|
|
470
|
-
lines.append(f" Mode: {template.agent_mode.value}")
|
|
471
|
-
lines.append(f" Purpose: {template.purpose}")
|
|
472
|
-
lines.append(f" Sections: {template.section_count}")
|
|
473
|
-
|
|
474
|
-
result = "\n".join(lines)
|
|
475
|
-
logger.debug("📄 Listed %d templates", len(templates))
|
|
476
|
-
return result
|
|
477
|
-
|
|
478
|
-
except Exception as e:
|
|
479
|
-
error_msg = f"Failed to list templates: {str(e)}"
|
|
480
|
-
logger.error("❌ List templates failed: %s", error_msg)
|
|
481
|
-
return f"Error: {error_msg}"
|
shotgun/artifacts/__init__.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""Artifact system for managing structured content in .shotgun directory."""
|
|
2
|
-
|
|
3
|
-
__all__ = [
|
|
4
|
-
"ArtifactService",
|
|
5
|
-
"ArtifactManager",
|
|
6
|
-
"Artifact",
|
|
7
|
-
"ArtifactSection",
|
|
8
|
-
"ArtifactSummary",
|
|
9
|
-
"AgentMode",
|
|
10
|
-
"generate_artifact_name",
|
|
11
|
-
"parse_agent_mode_string",
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
from .manager import ArtifactManager
|
|
15
|
-
from .models import AgentMode, Artifact, ArtifactSection, ArtifactSummary
|
|
16
|
-
from .service import ArtifactService
|
|
17
|
-
from .utils import generate_artifact_name, parse_agent_mode_string
|
shotgun/artifacts/exceptions.py
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"""Exception classes for the artifact system."""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class ArtifactError(Exception):
|
|
5
|
-
"""Base exception for all artifact-related errors."""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class ArtifactNotFoundError(ArtifactError):
|
|
9
|
-
"""Raised when an artifact is not found."""
|
|
10
|
-
|
|
11
|
-
def __init__(self, artifact_id: str, agent_mode: str | None = None) -> None:
|
|
12
|
-
if agent_mode:
|
|
13
|
-
message = f"Artifact '{artifact_id}' not found in agent mode '{agent_mode}'"
|
|
14
|
-
else:
|
|
15
|
-
message = f"Artifact '{artifact_id}' not found"
|
|
16
|
-
super().__init__(message)
|
|
17
|
-
self.artifact_id = artifact_id
|
|
18
|
-
self.agent_mode = agent_mode
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class SectionNotFoundError(ArtifactError):
|
|
22
|
-
"""Raised when a section is not found within an artifact."""
|
|
23
|
-
|
|
24
|
-
def __init__(self, section_identifier: str | int, artifact_id: str) -> None:
|
|
25
|
-
message = (
|
|
26
|
-
f"Section '{section_identifier}' not found in artifact '{artifact_id}'"
|
|
27
|
-
)
|
|
28
|
-
super().__init__(message)
|
|
29
|
-
self.section_identifier = section_identifier
|
|
30
|
-
self.artifact_id = artifact_id
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class SectionAlreadyExistsError(ArtifactError):
|
|
34
|
-
"""Raised when trying to create a section that already exists."""
|
|
35
|
-
|
|
36
|
-
def __init__(self, section_identifier: str | int, artifact_id: str) -> None:
|
|
37
|
-
message = (
|
|
38
|
-
f"Section '{section_identifier}' already exists in artifact '{artifact_id}'"
|
|
39
|
-
)
|
|
40
|
-
super().__init__(message)
|
|
41
|
-
self.section_identifier = section_identifier
|
|
42
|
-
self.artifact_id = artifact_id
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class ArtifactAlreadyExistsError(ArtifactError):
|
|
46
|
-
"""Raised when trying to create an artifact that already exists."""
|
|
47
|
-
|
|
48
|
-
def __init__(self, artifact_id: str, agent_mode: str) -> None:
|
|
49
|
-
message = (
|
|
50
|
-
f"Artifact '{artifact_id}' already exists in agent mode '{agent_mode}'"
|
|
51
|
-
)
|
|
52
|
-
super().__init__(message)
|
|
53
|
-
self.artifact_id = artifact_id
|
|
54
|
-
self.agent_mode = agent_mode
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class InvalidArtifactPathError(ArtifactError):
|
|
58
|
-
"""Raised when an artifact path is invalid or outside allowed directories."""
|
|
59
|
-
|
|
60
|
-
def __init__(self, path: str, reason: str | None = None) -> None:
|
|
61
|
-
message = f"Invalid artifact path: {path}"
|
|
62
|
-
if reason:
|
|
63
|
-
message += f" - {reason}"
|
|
64
|
-
super().__init__(message)
|
|
65
|
-
self.path = path
|
|
66
|
-
self.reason = reason
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class ArtifactFileSystemError(ArtifactError):
|
|
70
|
-
"""Raised when file system operations fail."""
|
|
71
|
-
|
|
72
|
-
def __init__(self, operation: str, path: str, reason: str) -> None:
|
|
73
|
-
message = f"File system error during {operation} on '{path}': {reason}"
|
|
74
|
-
super().__init__(message)
|
|
75
|
-
self.operation = operation
|
|
76
|
-
self.path = path
|
|
77
|
-
self.reason = reason
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class ArtifactValidationError(ArtifactError):
|
|
81
|
-
"""Raised when artifact data validation fails."""
|
|
82
|
-
|
|
83
|
-
def __init__(self, message: str, field: str | None = None) -> None:
|
|
84
|
-
if field:
|
|
85
|
-
message = f"Validation error for field '{field}': {message}"
|
|
86
|
-
else:
|
|
87
|
-
message = f"Validation error: {message}"
|
|
88
|
-
super().__init__(message)
|
|
89
|
-
self.field = field
|