shotgun-sh 0.1.0.dev26__tar.gz → 0.1.0.dev28__tar.gz

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.

Files changed (132) hide show
  1. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/PKG-INFO +1 -1
  2. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/pyproject.toml +1 -1
  3. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/agent_manager.py +46 -8
  4. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/common.py +10 -9
  5. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/history_processors.py +41 -25
  6. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/message_utils.py +39 -1
  7. shotgun_sh-0.1.0.dev28/src/shotgun/agents/messages.py +35 -0
  8. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/file_management.py +18 -14
  9. shotgun_sh-0.1.0.dev28/src/shotgun/prompts/agents/export.j2 +350 -0
  10. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/state/system_state.j2 +2 -4
  11. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/tasks.j2 +45 -7
  12. shotgun_sh-0.1.0.dev26/src/shotgun/prompts/agents/export.j2 +0 -214
  13. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/.gitignore +0 -0
  14. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/LICENSE +0 -0
  15. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/README.md +0 -0
  16. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/hatch_build.py +0 -0
  17. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/__init__.py +0 -0
  18. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/__init__.py +0 -0
  19. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/config/__init__.py +0 -0
  20. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/config/constants.py +0 -0
  21. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/config/manager.py +0 -0
  22. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/config/models.py +0 -0
  23. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/config/provider.py +0 -0
  24. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/conversation_history.py +0 -0
  25. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/conversation_manager.py +0 -0
  26. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/export.py +0 -0
  27. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/__init__.py +0 -0
  28. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/compaction.py +0 -0
  29. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/constants.py +0 -0
  30. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/context_extraction.py +0 -0
  31. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/history_building.py +0 -0
  32. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/token_counting.py +0 -0
  33. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/history/token_estimation.py +0 -0
  34. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/models.py +0 -0
  35. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/plan.py +0 -0
  36. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/research.py +0 -0
  37. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/specify.py +0 -0
  38. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tasks.py +0 -0
  39. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/__init__.py +0 -0
  40. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  41. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  42. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  43. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  44. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/models.py +0 -0
  45. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  46. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  47. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/user_interaction.py +0 -0
  48. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  49. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  50. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  51. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  52. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  53. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/build_constants.py +0 -0
  54. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/__init__.py +0 -0
  55. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/codebase/__init__.py +0 -0
  56. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/codebase/commands.py +0 -0
  57. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/codebase/models.py +0 -0
  58. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/config.py +0 -0
  59. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/export.py +0 -0
  60. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/models.py +0 -0
  61. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/plan.py +0 -0
  62. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/research.py +0 -0
  63. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/specify.py +0 -0
  64. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/tasks.py +0 -0
  65. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/update.py +0 -0
  66. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/cli/utils.py +0 -0
  67. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/__init__.py +0 -0
  68. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/__init__.py +0 -0
  69. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/change_detector.py +0 -0
  70. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  71. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/ingestor.py +0 -0
  72. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/language_config.py +0 -0
  73. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/manager.py +0 -0
  74. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/nl_query.py +0 -0
  75. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/core/parser_loader.py +0 -0
  76. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/models.py +0 -0
  77. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/codebase/service.py +0 -0
  78. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/logging_config.py +0 -0
  79. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/main.py +0 -0
  80. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/posthog_telemetry.py +0 -0
  81. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/__init__.py +0 -0
  82. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/__init__.py +0 -0
  83. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  84. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  85. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  86. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  87. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/plan.j2 +0 -0
  88. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/research.j2 +0 -0
  89. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/specify.j2 +0 -0
  90. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  91. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/__init__.py +0 -0
  92. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  93. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  94. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  95. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  96. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  97. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  98. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/history/__init__.py +0 -0
  99. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  100. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/history/summarization.j2 +0 -0
  101. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/prompts/loader.py +0 -0
  102. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/py.typed +0 -0
  103. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/sdk/__init__.py +0 -0
  104. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/sdk/codebase.py +0 -0
  105. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/sdk/exceptions.py +0 -0
  106. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/sdk/models.py +0 -0
  107. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/sdk/services.py +0 -0
  108. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/sentry_telemetry.py +0 -0
  109. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/telemetry.py +0 -0
  110. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/__init__.py +0 -0
  111. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/app.py +0 -0
  112. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/commands/__init__.py +0 -0
  113. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/components/prompt_input.py +0 -0
  114. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/components/spinner.py +0 -0
  115. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/components/splash.py +0 -0
  116. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/components/vertical_tail.py +0 -0
  117. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/chat.py +0 -0
  118. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/chat.tcss +0 -0
  119. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  120. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  121. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
  122. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
  123. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/directory_setup.py +0 -0
  124. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/provider_config.py +0 -0
  125. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/screens/splash.py +0 -0
  126. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/styles.tcss +0 -0
  127. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/utils/__init__.py +0 -0
  128. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/tui/utils/mode_progress.py +0 -0
  129. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/utils/__init__.py +0 -0
  130. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/utils/env_utils.py +0 -0
  131. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/utils/file_system_utils.py +0 -0
  132. {shotgun_sh-0.1.0.dev26 → shotgun_sh-0.1.0.dev28}/src/shotgun/utils/update_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgun-sh
3
- Version: 0.1.0.dev26
3
+ Version: 0.1.0.dev28
4
4
  Summary: AI-powered research, planning, and task management CLI tool
5
5
  Project-URL: Homepage, https://shotgun.sh/
6
6
  Project-URL: Repository, https://github.com/shotgun-sh/shotgun
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shotgun-sh"
3
- version = "0.1.0.dev26"
3
+ version = "0.1.0.dev28"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -41,6 +41,7 @@ from shotgun.tui.screens.chat_screen.hint_message import HintMessage
41
41
 
42
42
  from .export import create_export_agent
43
43
  from .history.compaction import apply_persistent_compaction
44
+ from .messages import AgentSystemPrompt
44
45
  from .models import AgentDeps, AgentRuntimeOptions
45
46
  from .plan import create_plan_agent
46
47
  from .research import create_research_agent
@@ -269,6 +270,9 @@ class AgentManager(Widget):
269
270
  if deps is None:
270
271
  raise ValueError("AgentDeps must be provided")
271
272
 
273
+ # Clear file tracker before each run to track only this run's operations
274
+ deps.file_tracker.clear()
275
+
272
276
  if prompt:
273
277
  self.ui_message_history.append(ModelRequest.user_text_prompt(prompt))
274
278
  self._post_messages_updated()
@@ -278,15 +282,51 @@ class AgentManager(Widget):
278
282
 
279
283
  deps.agent_mode = self._current_agent_type
280
284
 
285
+ # Filter out system prompts from other agent types
286
+ from pydantic_ai.messages import ModelRequestPart
287
+
288
+ filtered_history: list[ModelMessage] = []
289
+ for message in message_history:
290
+ # Keep all non-ModelRequest messages as-is
291
+ if not isinstance(message, ModelRequest):
292
+ filtered_history.append(message)
293
+ continue
294
+
295
+ # Filter out AgentSystemPrompts from other agent types
296
+ filtered_parts: list[ModelRequestPart] = []
297
+ for part in message.parts:
298
+ # Keep non-AgentSystemPrompt parts
299
+ if not isinstance(part, AgentSystemPrompt):
300
+ filtered_parts.append(part)
301
+ continue
302
+
303
+ # Only keep system prompts from the same agent type
304
+ if part.agent_mode == deps.agent_mode:
305
+ filtered_parts.append(part)
306
+
307
+ # Only add the message if it has parts remaining
308
+ if filtered_parts:
309
+ filtered_history.append(ModelRequest(parts=filtered_parts))
310
+
311
+ message_history = filtered_history
312
+
281
313
  # Add a system status message so the agent knows whats going on
282
314
  message_history = await add_system_status_message(deps, message_history)
283
315
 
284
- # Check if the message history already has a system prompt
285
- has_system_prompt = any(
286
- hasattr(msg, "parts")
287
- and any(isinstance(part, SystemPromptPart) for part in msg.parts)
288
- for msg in message_history
289
- )
316
+ # Check if the message history already has a system prompt from the same agent type
317
+ has_system_prompt = False
318
+ for message in message_history:
319
+ if not isinstance(message, ModelRequest):
320
+ continue
321
+
322
+ for part in message.parts:
323
+ if not isinstance(part, AgentSystemPrompt):
324
+ continue
325
+
326
+ # Check if it's from the same agent type
327
+ if part.agent_mode == deps.agent_mode:
328
+ has_system_prompt = True
329
+ break
290
330
 
291
331
  # Always ensure we have a system prompt for the agent
292
332
  # (compaction may remove it from persistent history, but agent needs it)
@@ -472,8 +512,6 @@ class AgentManager(Widget):
472
512
  Returns:
473
513
  List of messages without system prompt parts
474
514
  """
475
- from pydantic_ai.messages import SystemPromptPart
476
-
477
515
  filtered_messages: list[ModelMessage | HintMessage] = []
478
516
  for msg in messages:
479
517
  if isinstance(msg, HintMessage):
@@ -16,7 +16,6 @@ from pydantic_ai.agent import AgentRunResult
16
16
  from pydantic_ai.messages import (
17
17
  ModelMessage,
18
18
  ModelRequest,
19
- SystemPromptPart,
20
19
  )
21
20
 
22
21
  from shotgun.agents.config import ProviderType, get_config_manager, get_provider_model
@@ -29,6 +28,7 @@ from shotgun.utils.file_system_utils import get_shotgun_base_path
29
28
 
30
29
  from .history import token_limit_compactor
31
30
  from .history.compaction import apply_persistent_compaction
31
+ from .messages import AgentSystemPrompt, SystemStatusPrompt
32
32
  from .models import AgentDeps, AgentRuntimeOptions, PipelineConfigEntry
33
33
  from .tools import (
34
34
  append_file,
@@ -85,7 +85,7 @@ async def add_system_status_message(
85
85
  message_history.append(
86
86
  ModelRequest(
87
87
  parts=[
88
- SystemPromptPart(content=system_state),
88
+ SystemStatusPrompt(content=system_state),
89
89
  ]
90
90
  )
91
91
  )
@@ -315,10 +315,9 @@ def extract_markdown_toc(agent_mode: AgentType | None) -> str | None:
315
315
  # Only show # and ## headings from prior files, max 500 chars each
316
316
  prior_toc = _extract_file_toc_content(file_path, max_depth=2, max_chars=500)
317
317
  if prior_toc:
318
- # Add section header
319
- file_label = prior_file.replace(".md", "").replace("_", " ").title()
318
+ # Add section with XML tags
320
319
  toc_sections.append(
321
- f"=== Prior Context: {file_label} (summary) ===\n{prior_toc}"
320
+ f'<TABLE_OF_CONTENTS file_name="{prior_file}">\n{prior_toc}\n</TABLE_OF_CONTENTS>'
322
321
  )
323
322
 
324
323
  # Extract TOC from own file (full detail)
@@ -326,10 +325,10 @@ def extract_markdown_toc(agent_mode: AgentType | None) -> str | None:
326
325
  own_path = base_path / config.own_file
327
326
  own_toc = _extract_file_toc_content(own_path, max_depth=None, max_chars=2000)
328
327
  if own_toc:
329
- file_label = config.own_file.replace(".md", "").replace("_", " ").title()
330
- # Put own file TOC at the beginning
328
+ # Put own file TOC at the beginning with XML tags
331
329
  toc_sections.insert(
332
- 0, f"=== Your Current Document: {file_label} ===\n{own_toc}"
330
+ 0,
331
+ f'<TABLE_OF_CONTENTS file_name="{config.own_file}">\n{own_toc}\n</TABLE_OF_CONTENTS>',
333
332
  )
334
333
 
335
334
  # Combine all sections
@@ -484,7 +483,9 @@ async def add_system_prompt_message(
484
483
 
485
484
  # Create system message and prepend to message history
486
485
  system_message = ModelRequest(
487
- parts=[SystemPromptPart(content=system_prompt_content)]
486
+ parts=[
487
+ AgentSystemPrompt(content=system_prompt_content, agent_mode=deps.agent_mode)
488
+ ]
488
489
  )
489
490
  message_history.insert(0, system_message)
490
491
  logger.debug("✅ System prompt prepended as first message")
@@ -6,12 +6,12 @@ from pydantic_ai.messages import (
6
6
  ModelMessage,
7
7
  ModelRequest,
8
8
  ModelResponse,
9
- SystemPromptPart,
10
9
  TextPart,
11
10
  UserPromptPart,
12
11
  )
13
12
 
14
13
  from shotgun.agents.config.models import shotgun_model_request
14
+ from shotgun.agents.messages import AgentSystemPrompt, SystemStatusPrompt
15
15
  from shotgun.agents.models import AgentDeps
16
16
  from shotgun.logging_config import get_logger
17
17
  from shotgun.prompts import PromptLoader
@@ -20,8 +20,9 @@ from .constants import SUMMARY_MARKER, TOKEN_LIMIT_RATIO
20
20
  from .context_extraction import extract_context_from_messages
21
21
  from .history_building import ensure_ends_with_model_request
22
22
  from .message_utils import (
23
+ get_agent_system_prompt,
23
24
  get_first_user_request,
24
- get_system_prompt,
25
+ get_latest_system_status,
25
26
  )
26
27
  from .token_estimation import (
27
28
  calculate_max_summarization_tokens as _calculate_max_summarization_tokens,
@@ -274,31 +275,39 @@ async def token_limit_compactor(
274
275
  new_summary_part = create_marked_summary_part(summary_response)
275
276
 
276
277
  # Extract essential context from messages before the last summary (if any)
277
- system_prompt = ""
278
+ agent_prompt = ""
279
+ system_status = ""
278
280
  first_user_prompt = ""
279
281
  if last_summary_index > 0:
280
- # Get system and first user from original conversation
281
- system_prompt = get_system_prompt(messages[:last_summary_index]) or ""
282
+ # Get agent system prompt and first user from original conversation
283
+ agent_prompt = get_agent_system_prompt(messages[:last_summary_index]) or ""
282
284
  first_user_prompt = (
283
285
  get_first_user_request(messages[:last_summary_index]) or ""
284
286
  )
285
287
 
288
+ # Get the latest system status from all messages
289
+ system_status = get_latest_system_status(messages) or ""
290
+
286
291
  # Create the updated summary message
287
292
  updated_summary_message = ModelResponse(parts=[new_summary_part])
288
293
 
289
294
  # Build final compacted history with CLEAN structure
290
295
  compacted_messages: list[ModelMessage] = []
291
296
 
292
- # Only add system/user context if it exists and is meaningful
293
- if system_prompt or first_user_prompt:
294
- compacted_messages.append(
295
- ModelRequest(
296
- parts=[
297
- SystemPromptPart(content=system_prompt),
298
- UserPromptPart(content=first_user_prompt),
299
- ]
300
- )
301
- )
297
+ # Build parts for the initial request
298
+ from pydantic_ai.messages import ModelRequestPart
299
+
300
+ parts: list[ModelRequestPart] = []
301
+ if agent_prompt:
302
+ parts.append(AgentSystemPrompt(content=agent_prompt))
303
+ if system_status:
304
+ parts.append(SystemStatusPrompt(content=system_status))
305
+ if first_user_prompt:
306
+ parts.append(UserPromptPart(content=first_user_prompt))
307
+
308
+ # Only add if we have at least one part
309
+ if parts:
310
+ compacted_messages.append(ModelRequest(parts=parts))
302
311
 
303
312
  # Add the summary
304
313
  compacted_messages.append(updated_summary_message)
@@ -390,19 +399,26 @@ async def _full_compaction(
390
399
  marked_summary_part = create_marked_summary_part(summary_response)
391
400
 
392
401
  # Build compacted history structure
393
- system_prompt = get_system_prompt(messages) or ""
402
+ agent_prompt = get_agent_system_prompt(messages) or ""
403
+ system_status = get_latest_system_status(messages) or ""
394
404
  user_prompt = get_first_user_request(messages) or ""
395
405
 
406
+ # Build parts for the initial request
407
+ from pydantic_ai.messages import ModelRequestPart
408
+
409
+ parts: list[ModelRequestPart] = []
410
+ if agent_prompt:
411
+ parts.append(AgentSystemPrompt(content=agent_prompt))
412
+ if system_status:
413
+ parts.append(SystemStatusPrompt(content=system_status))
414
+ if user_prompt:
415
+ parts.append(UserPromptPart(content=user_prompt))
416
+
396
417
  # Create base structure
397
- compacted_messages: list[ModelMessage] = [
398
- ModelRequest(
399
- parts=[
400
- SystemPromptPart(content=system_prompt),
401
- UserPromptPart(content=user_prompt),
402
- ]
403
- ),
404
- ModelResponse(parts=[marked_summary_part]),
405
- ]
418
+ compacted_messages: list[ModelMessage] = []
419
+ if parts:
420
+ compacted_messages.append(ModelRequest(parts=parts))
421
+ compacted_messages.append(ModelResponse(parts=[marked_summary_part]))
406
422
 
407
423
  # Ensure history ends with ModelRequest for PydanticAI compatibility
408
424
  compacted_messages = ensure_ends_with_model_request(compacted_messages, messages)
@@ -7,6 +7,8 @@ from pydantic_ai.messages import (
7
7
  UserPromptPart,
8
8
  )
9
9
 
10
+ from shotgun.agents.messages import AgentSystemPrompt, SystemStatusPrompt
11
+
10
12
 
11
13
  def get_first_user_request(messages: list[ModelMessage]) -> str | None:
12
14
  """Extract first user request content from messages."""
@@ -37,10 +39,46 @@ def get_user_content_from_request(request: ModelRequest) -> str | None:
37
39
 
38
40
 
39
41
  def get_system_prompt(messages: list[ModelMessage]) -> str | None:
40
- """Extract system prompt from messages."""
42
+ """Extract system prompt from messages (any SystemPromptPart)."""
41
43
  for msg in messages:
42
44
  if isinstance(msg, ModelRequest):
43
45
  for part in msg.parts:
44
46
  if isinstance(part, SystemPromptPart):
45
47
  return part.content
46
48
  return None
49
+
50
+
51
+ def get_agent_system_prompt(messages: list[ModelMessage]) -> str | None:
52
+ """Extract the main agent system prompt from messages.
53
+
54
+ Prioritizes AgentSystemPrompt but falls back to generic SystemPromptPart
55
+ if no AgentSystemPrompt is found.
56
+ """
57
+ # First try to find AgentSystemPrompt
58
+ for msg in messages:
59
+ if isinstance(msg, ModelRequest):
60
+ for part in msg.parts:
61
+ if isinstance(part, AgentSystemPrompt):
62
+ return part.content
63
+
64
+ # Fall back to any SystemPromptPart (excluding SystemStatusPrompt)
65
+ for msg in messages:
66
+ if isinstance(msg, ModelRequest):
67
+ for part in msg.parts:
68
+ if isinstance(part, SystemPromptPart) and not isinstance(
69
+ part, SystemStatusPrompt
70
+ ):
71
+ return part.content
72
+
73
+ return None
74
+
75
+
76
+ def get_latest_system_status(messages: list[ModelMessage]) -> str | None:
77
+ """Extract the most recent system status prompt from messages."""
78
+ # Iterate in reverse to find the most recent status
79
+ for msg in reversed(messages):
80
+ if isinstance(msg, ModelRequest):
81
+ for part in msg.parts:
82
+ if isinstance(part, SystemStatusPrompt):
83
+ return part.content
84
+ return None
@@ -0,0 +1,35 @@
1
+ """Custom message types for Shotgun agents.
2
+
3
+ This module defines specialized SystemPromptPart subclasses to distinguish
4
+ between different types of system prompts in the agent pipeline.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+
9
+ from pydantic_ai.messages import SystemPromptPart
10
+
11
+ from shotgun.agents.models import AgentType
12
+
13
+
14
+ @dataclass
15
+ class AgentSystemPrompt(SystemPromptPart):
16
+ """System prompt containing the main agent instructions.
17
+
18
+ This is the primary system prompt that defines the agent's role,
19
+ capabilities, and behavior. It should be preserved during compaction.
20
+ """
21
+
22
+ prompt_type: str = "agent"
23
+ agent_mode: AgentType | None = field(default=None)
24
+
25
+
26
+ @dataclass
27
+ class SystemStatusPrompt(SystemPromptPart):
28
+ """System prompt containing current system status information.
29
+
30
+ This includes table of contents, available files, and other contextual
31
+ information about the current state. Only the most recent status should
32
+ be preserved during compaction.
33
+ """
34
+
35
+ prompt_type: str = "status"
@@ -20,7 +20,15 @@ AGENT_DIRECTORIES = {
20
20
  AgentType.SPECIFY: "specification.md",
21
21
  AgentType.PLAN: "plan.md",
22
22
  AgentType.TASKS: "tasks.md",
23
- AgentType.EXPORT: "exports/",
23
+ AgentType.EXPORT: "*", # Export agent can write anywhere except protected files
24
+ }
25
+
26
+ # Files protected from export agent modifications
27
+ PROTECTED_AGENT_FILES = {
28
+ "research.md",
29
+ "specification.md",
30
+ "plan.md",
31
+ "tasks.md",
24
32
  }
25
33
 
26
34
 
@@ -40,21 +48,17 @@ def _validate_agent_scoped_path(filename: str, agent_mode: AgentType | None) ->
40
48
  base_path = get_shotgun_base_path()
41
49
 
42
50
  if agent_mode and agent_mode in AGENT_DIRECTORIES:
43
- # For export mode, allow writing to any file in exports/ directory
51
+ # For export mode, allow writing to any file except protected agent files
44
52
  if agent_mode == AgentType.EXPORT:
45
- # Ensure the filename starts with exports/ or is being written to exports/
46
- if not filename.startswith("exports/"):
47
- filename = f"exports/{filename}"
48
- full_path = (base_path / filename).resolve()
49
-
50
- # Ensure it's within .shotgun/exports/
51
- exports_dir = base_path / "exports"
52
- try:
53
- full_path.relative_to(exports_dir.resolve())
54
- except ValueError as e:
53
+ # Check if trying to write to a protected file
54
+ if filename in PROTECTED_AGENT_FILES:
55
55
  raise ValueError(
56
- f"Export agent can only write to exports/ directory. Path '{filename}' is not allowed"
57
- ) from e
56
+ f"Export agent cannot write to protected file '{filename}'. "
57
+ f"Protected files are: {', '.join(sorted(PROTECTED_AGENT_FILES))}"
58
+ )
59
+
60
+ # Allow writing anywhere else in .shotgun directory
61
+ full_path = (base_path / filename).resolve()
58
62
  else:
59
63
  # For other agents, only allow writing to their specific file
60
64
  allowed_file = AGENT_DIRECTORIES[agent_mode]