shotgun-sh 0.1.0.dev15__tar.gz → 0.1.0.dev17__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 (145) hide show
  1. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/PKG-INFO +1 -1
  2. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/pyproject.toml +1 -1
  3. shotgun_sh-0.1.0.dev17/src/shotgun/agents/agent_manager.py +439 -0
  4. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/artifact_management.py +2 -3
  5. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/research/sdk_comparison.yaml +73 -73
  6. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/partials/artifact_system.j2 +0 -3
  7. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +10 -1
  8. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/plan.j2 +9 -12
  9. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/research.j2 +6 -3
  10. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/specify.j2 +8 -9
  11. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/state/artifact_templates_available.j2 +3 -1
  12. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/state/existing_artifacts_available.j2 +2 -0
  13. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/tasks.j2 +1 -1
  14. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +2 -0
  15. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +4 -2
  16. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/app.py +9 -1
  17. shotgun_sh-0.1.0.dev17/src/shotgun/tui/commands/__init__.py +73 -0
  18. shotgun_sh-0.1.0.dev17/src/shotgun/tui/screens/chat.py +680 -0
  19. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/screens/chat.tcss +11 -0
  20. shotgun_sh-0.1.0.dev17/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  21. shotgun_sh-0.1.0.dev17/src/shotgun/tui/screens/chat_screen/command_providers.py +203 -0
  22. shotgun_sh-0.1.0.dev17/src/shotgun/tui/screens/chat_screen/history.py +175 -0
  23. shotgun_sh-0.1.0.dev15/src/shotgun/agents/agent_manager.py +0 -220
  24. shotgun_sh-0.1.0.dev15/src/shotgun/tui/screens/chat.py +0 -497
  25. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/.gitignore +0 -0
  26. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/LICENSE +0 -0
  27. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/README.md +0 -0
  28. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/hatch_build.py +0 -0
  29. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/__init__.py +0 -0
  30. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/__init__.py +0 -0
  31. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/artifact_state.py +0 -0
  32. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/common.py +0 -0
  33. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/config/__init__.py +0 -0
  34. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/config/constants.py +0 -0
  35. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/config/manager.py +0 -0
  36. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/config/models.py +0 -0
  37. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/config/provider.py +0 -0
  38. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/__init__.py +0 -0
  39. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/compaction.py +0 -0
  40. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/constants.py +0 -0
  41. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/context_extraction.py +0 -0
  42. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/history_building.py +0 -0
  43. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/history_processors.py +0 -0
  44. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/message_utils.py +0 -0
  45. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/token_counting.py +0 -0
  46. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/history/token_estimation.py +0 -0
  47. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/models.py +0 -0
  48. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/plan.py +0 -0
  49. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/research.py +0 -0
  50. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/specify.py +0 -0
  51. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tasks.py +0 -0
  52. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/__init__.py +0 -0
  53. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  54. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  55. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  56. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  57. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/models.py +0 -0
  58. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  59. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  60. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/file_management.py +0 -0
  61. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/user_interaction.py +0 -0
  62. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  63. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  64. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  65. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  66. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  67. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/__init__.py +0 -0
  68. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/exceptions.py +0 -0
  69. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/manager.py +0 -0
  70. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/models.py +0 -0
  71. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/service.py +0 -0
  72. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/__init__.py +0 -0
  73. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/loader.py +0 -0
  74. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/models.py +0 -0
  75. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +0 -0
  76. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/research/market_research.yaml +0 -0
  77. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/specify/prd.yaml +0 -0
  78. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/templates/specify/product_spec.yaml +0 -0
  79. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/artifacts/utils.py +0 -0
  80. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/build_constants.py +0 -0
  81. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/__init__.py +0 -0
  82. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/codebase/__init__.py +0 -0
  83. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/codebase/commands.py +0 -0
  84. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/codebase/models.py +0 -0
  85. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/config.py +0 -0
  86. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/models.py +0 -0
  87. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/plan.py +0 -0
  88. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/research.py +0 -0
  89. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/specify.py +0 -0
  90. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/tasks.py +0 -0
  91. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/update.py +0 -0
  92. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/cli/utils.py +0 -0
  93. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/__init__.py +0 -0
  94. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/__init__.py +0 -0
  95. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/change_detector.py +0 -0
  96. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  97. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/ingestor.py +0 -0
  98. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/language_config.py +0 -0
  99. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/manager.py +0 -0
  100. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/nl_query.py +0 -0
  101. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/core/parser_loader.py +0 -0
  102. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/models.py +0 -0
  103. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/codebase/service.py +0 -0
  104. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/logging_config.py +0 -0
  105. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/main.py +0 -0
  106. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/posthog_telemetry.py +0 -0
  107. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/__init__.py +0 -0
  108. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/__init__.py +0 -0
  109. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  110. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  111. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  112. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  113. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  114. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/__init__.py +0 -0
  115. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  116. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  117. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  118. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  119. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/history/__init__.py +0 -0
  120. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  121. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/history/summarization.j2 +0 -0
  122. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/prompts/loader.py +0 -0
  123. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/py.typed +0 -0
  124. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/__init__.py +0 -0
  125. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/artifact_models.py +0 -0
  126. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/artifacts.py +0 -0
  127. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/codebase.py +0 -0
  128. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/exceptions.py +0 -0
  129. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/models.py +0 -0
  130. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sdk/services.py +0 -0
  131. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/sentry_telemetry.py +0 -0
  132. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/telemetry.py +0 -0
  133. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/__init__.py +0 -0
  134. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/components/prompt_input.py +0 -0
  135. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/components/spinner.py +0 -0
  136. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/components/splash.py +0 -0
  137. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/components/vertical_tail.py +0 -0
  138. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/screens/directory_setup.py +0 -0
  139. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/screens/provider_config.py +0 -0
  140. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/screens/splash.py +0 -0
  141. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/tui/styles.tcss +0 -0
  142. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/utils/__init__.py +0 -0
  143. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/utils/env_utils.py +0 -0
  144. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/src/shotgun/utils/file_system_utils.py +0 -0
  145. {shotgun_sh-0.1.0.dev15 → shotgun_sh-0.1.0.dev17}/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.dev15
3
+ Version: 0.1.0.dev17
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.dev15"
3
+ version = "0.1.0.dev17"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -0,0 +1,439 @@
1
+ """Agent manager for coordinating multiple AI agents with shared message history."""
2
+
3
+ import logging
4
+ from collections.abc import AsyncIterable
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import Any, cast
8
+
9
+ from pydantic_ai import (
10
+ Agent,
11
+ DeferredToolRequests,
12
+ DeferredToolResults,
13
+ RunContext,
14
+ UsageLimits,
15
+ )
16
+ from pydantic_ai.agent import AgentRunResult
17
+ from pydantic_ai.messages import (
18
+ AgentStreamEvent,
19
+ FinalResultEvent,
20
+ ModelMessage,
21
+ ModelRequest,
22
+ ModelResponse,
23
+ ModelResponsePart,
24
+ PartDeltaEvent,
25
+ PartStartEvent,
26
+ ToolCallPartDelta,
27
+ )
28
+ from textual.message import Message
29
+ from textual.widget import Widget
30
+
31
+ from .history.compaction import apply_persistent_compaction
32
+ from .models import AgentDeps, AgentRuntimeOptions, FileOperation
33
+ from .plan import create_plan_agent
34
+ from .research import create_research_agent
35
+ from .specify import create_specify_agent
36
+ from .tasks import create_tasks_agent
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ class AgentType(Enum):
42
+ """Enumeration for available agent types (for Python < 3.11)."""
43
+
44
+ RESEARCH = "research"
45
+ PLAN = "plan"
46
+ TASKS = "tasks"
47
+ SPECIFY = "specify"
48
+
49
+
50
+ class MessageHistoryUpdated(Message):
51
+ """Event posted when the message history is updated."""
52
+
53
+ def __init__(
54
+ self,
55
+ messages: list[ModelMessage],
56
+ agent_type: AgentType,
57
+ file_operations: list[FileOperation] | None = None,
58
+ ) -> None:
59
+ """Initialize the message history updated event.
60
+
61
+ Args:
62
+ messages: The updated message history.
63
+ agent_type: The type of agent that triggered the update.
64
+ file_operations: List of file operations from this run.
65
+ """
66
+ super().__init__()
67
+ self.messages = messages
68
+ self.agent_type = agent_type
69
+ self.file_operations = file_operations or []
70
+
71
+
72
+ class PartialResponseMessage(Message):
73
+ """Event posted when a partial response is received."""
74
+
75
+ def __init__(self, message: ModelResponse | None, is_last: bool) -> None:
76
+ """Initialize the partial response message."""
77
+ super().__init__()
78
+ self.message = message
79
+ self.is_last = is_last
80
+
81
+
82
+ @dataclass(slots=True)
83
+ class _PartialStreamState:
84
+ """Tracks partial response parts while streaming a single agent run."""
85
+
86
+ parts: list[ModelResponsePart | ToolCallPartDelta] = field(default_factory=list)
87
+ latest_partial: ModelResponse | None = None
88
+ final_sent: bool = False
89
+
90
+
91
+ class AgentManager(Widget):
92
+ """Manages multiple agents with shared message history."""
93
+
94
+ def __init__(
95
+ self,
96
+ deps: AgentDeps | None = None,
97
+ initial_type: AgentType = AgentType.RESEARCH,
98
+ ) -> None:
99
+ """Initialize the agent manager.
100
+
101
+ Args:
102
+ deps: Optional agent dependencies. If not provided, defaults to interactive mode.
103
+ """
104
+ super().__init__()
105
+ self.display = False
106
+
107
+ # Use provided deps or create default with interactive mode
108
+ self.deps = deps
109
+
110
+ if self.deps is None:
111
+ raise ValueError("AgentDeps must be provided to AgentManager")
112
+
113
+ # Create AgentRuntimeOptions from deps for agent creation
114
+ agent_runtime_options = AgentRuntimeOptions(
115
+ interactive_mode=self.deps.interactive_mode,
116
+ working_directory=self.deps.working_directory,
117
+ max_iterations=self.deps.max_iterations,
118
+ queue=self.deps.queue,
119
+ tasks=self.deps.tasks,
120
+ )
121
+
122
+ # Initialize all agents and store their specific deps
123
+ self.research_agent, self.research_deps = create_research_agent(
124
+ agent_runtime_options=agent_runtime_options
125
+ )
126
+ self.plan_agent, self.plan_deps = create_plan_agent(
127
+ agent_runtime_options=agent_runtime_options
128
+ )
129
+ self.tasks_agent, self.tasks_deps = create_tasks_agent(
130
+ agent_runtime_options=agent_runtime_options
131
+ )
132
+ self.specify_agent, self.specify_deps = create_specify_agent(
133
+ agent_runtime_options=agent_runtime_options
134
+ )
135
+
136
+ # Track current active agent
137
+ self._current_agent_type: AgentType = initial_type
138
+
139
+ # Maintain shared message history
140
+ self.ui_message_history: list[ModelMessage] = []
141
+ self.message_history: list[ModelMessage] = []
142
+ self.recently_change_files: list[FileOperation] = []
143
+ self._stream_state: _PartialStreamState | None = None
144
+
145
+ @property
146
+ def current_agent(self) -> Agent[AgentDeps, str | DeferredToolRequests]:
147
+ """Get the currently active agent.
148
+
149
+ Returns:
150
+ The currently selected agent instance.
151
+ """
152
+ return self._get_agent(self._current_agent_type)
153
+
154
+ def _get_agent(
155
+ self, agent_type: AgentType
156
+ ) -> Agent[AgentDeps, str | DeferredToolRequests]:
157
+ """Get agent by type.
158
+
159
+ Args:
160
+ agent_type: The type of agent to retrieve.
161
+
162
+ Returns:
163
+ The requested agent instance.
164
+ """
165
+ agent_map = {
166
+ AgentType.RESEARCH: self.research_agent,
167
+ AgentType.PLAN: self.plan_agent,
168
+ AgentType.TASKS: self.tasks_agent,
169
+ AgentType.SPECIFY: self.specify_agent,
170
+ }
171
+ return agent_map[agent_type]
172
+
173
+ def _get_agent_deps(self, agent_type: AgentType) -> AgentDeps:
174
+ """Get agent-specific deps by type.
175
+
176
+ Args:
177
+ agent_type: The type of agent to retrieve deps for.
178
+
179
+ Returns:
180
+ The agent-specific dependencies.
181
+ """
182
+ deps_map = {
183
+ AgentType.RESEARCH: self.research_deps,
184
+ AgentType.PLAN: self.plan_deps,
185
+ AgentType.TASKS: self.tasks_deps,
186
+ AgentType.SPECIFY: self.specify_deps,
187
+ }
188
+ return deps_map[agent_type]
189
+
190
+ def _create_merged_deps(self, agent_type: AgentType) -> AgentDeps:
191
+ """Create merged dependencies combining shared and agent-specific deps.
192
+
193
+ This preserves the agent's system_prompt_fn while using shared runtime state.
194
+
195
+ Args:
196
+ agent_type: The type of agent to create merged deps for.
197
+
198
+ Returns:
199
+ Merged AgentDeps with agent-specific system_prompt_fn.
200
+ """
201
+ agent_deps = self._get_agent_deps(agent_type)
202
+
203
+ # Ensure shared deps is not None (should be guaranteed by __init__)
204
+ if self.deps is None:
205
+ raise ValueError("Shared deps is None - this should not happen")
206
+
207
+ # Create new deps with shared runtime state but agent's system_prompt_fn
208
+ # Use a copy of the shared deps and update the system_prompt_fn
209
+ merged_deps = self.deps.model_copy(
210
+ update={"system_prompt_fn": agent_deps.system_prompt_fn}
211
+ )
212
+
213
+ return merged_deps
214
+
215
+ def set_agent(self, agent_type: AgentType) -> None:
216
+ """Set the current active agent.
217
+
218
+ Args:
219
+ agent_type: The agent type to activate (AgentType enum or string).
220
+
221
+ Raises:
222
+ ValueError: If invalid agent type is provided.
223
+ """
224
+ try:
225
+ self._current_agent_type = AgentType(agent_type)
226
+ except ValueError:
227
+ raise ValueError(
228
+ f"Invalid agent type: {agent_type}. Must be one of: {', '.join(e.value for e in AgentType)}"
229
+ ) from None
230
+
231
+ async def run(
232
+ self,
233
+ prompt: str | None = None,
234
+ *,
235
+ deps: AgentDeps | None = None,
236
+ usage_limits: UsageLimits | None = None,
237
+ deferred_tool_results: DeferredToolResults | None = None,
238
+ **kwargs: Any,
239
+ ) -> AgentRunResult[str | DeferredToolRequests]:
240
+ """Run the current agent with automatic message history management.
241
+
242
+ This method wraps the agent's run method, automatically injecting the
243
+ shared message history and updating it after each run.
244
+
245
+ Args:
246
+ prompt: Optional prompt to send to the agent.
247
+ deps: Optional dependencies override (defaults to manager's deps).
248
+ usage_limits: Optional usage limits for the agent run.
249
+ deferred_tool_results: Optional deferred tool results for continuing a conversation.
250
+ **kwargs: Additional keyword arguments to pass to the agent.
251
+
252
+ Returns:
253
+ The agent run result.
254
+ """
255
+ # Use merged deps (shared state + agent-specific system prompt) if not provided
256
+ if deps is None:
257
+ deps = self._create_merged_deps(self._current_agent_type)
258
+
259
+ # Ensure deps is not None
260
+ if deps is None:
261
+ raise ValueError("AgentDeps must be provided")
262
+
263
+ if prompt:
264
+ self.ui_message_history.append(ModelRequest.user_text_prompt(prompt))
265
+ self._post_messages_updated()
266
+
267
+ # Ensure system prompt is added to message history before running agent
268
+ from pydantic_ai.messages import SystemPromptPart
269
+
270
+ from shotgun.agents.common import add_system_prompt_message
271
+
272
+ # Start with persistent message history
273
+ message_history = self.message_history
274
+
275
+ # Check if the message history already has a system prompt
276
+ has_system_prompt = any(
277
+ hasattr(msg, "parts")
278
+ and any(isinstance(part, SystemPromptPart) for part in msg.parts)
279
+ for msg in message_history
280
+ )
281
+
282
+ # Always ensure we have a system prompt for the agent
283
+ # (compaction may remove it from persistent history, but agent needs it)
284
+ if not has_system_prompt:
285
+ message_history = await add_system_prompt_message(deps, message_history)
286
+
287
+ # Run the agent with streaming support (from origin/main)
288
+ self._stream_state = _PartialStreamState()
289
+
290
+ model_name = ""
291
+ if hasattr(deps, "llm_model") and deps.llm_model is not None:
292
+ model_name = deps.llm_model.name
293
+ is_gpt5 = ( # streaming is likely not supported for gpt5. It varies between keys.
294
+ "gpt-5" in model_name.lower()
295
+ )
296
+
297
+ try:
298
+ result: AgentRunResult[
299
+ str | DeferredToolRequests
300
+ ] = await self.current_agent.run(
301
+ prompt,
302
+ deps=deps,
303
+ usage_limits=usage_limits,
304
+ message_history=message_history,
305
+ deferred_tool_results=deferred_tool_results,
306
+ event_stream_handler=self._handle_event_stream if not is_gpt5 else None,
307
+ **kwargs,
308
+ )
309
+ finally:
310
+ # If the stream ended unexpectedly without a final result, clear accumulated state.
311
+ if self._stream_state is not None and not self._stream_state.final_sent:
312
+ partial_message = self._build_partial_response(self._stream_state.parts)
313
+ if partial_message is not None:
314
+ self._post_partial_message(partial_message, True)
315
+ self._stream_state = None
316
+
317
+ self.ui_message_history = self.ui_message_history + [
318
+ mes for mes in result.new_messages() if not isinstance(mes, ModelRequest)
319
+ ]
320
+
321
+ # Apply compaction to persistent message history to prevent cascading growth
322
+ all_messages = result.all_messages()
323
+ self.message_history = await apply_persistent_compaction(all_messages, deps)
324
+
325
+ # Log file operations summary if any files were modified
326
+ file_operations = deps.file_tracker.operations.copy()
327
+ self.recently_change_files = file_operations
328
+
329
+ self._post_messages_updated(file_operations)
330
+
331
+ return result
332
+
333
+ async def _handle_event_stream(
334
+ self,
335
+ _ctx: RunContext[AgentDeps],
336
+ stream: AsyncIterable[AgentStreamEvent],
337
+ ) -> None:
338
+ """Process streamed events and forward partial updates to the UI."""
339
+
340
+ state = self._stream_state
341
+ if state is None:
342
+ state = self._stream_state = _PartialStreamState()
343
+
344
+ partial_parts = state.parts
345
+
346
+ async for event in stream:
347
+ try:
348
+ if isinstance(event, PartStartEvent):
349
+ index = event.index
350
+ if index < len(partial_parts):
351
+ partial_parts[index] = event.part
352
+ elif index == len(partial_parts):
353
+ partial_parts.append(event.part)
354
+ else:
355
+ logger.warning(
356
+ "Received PartStartEvent with out-of-bounds index",
357
+ extra={"index": index, "current_len": len(partial_parts)},
358
+ )
359
+ partial_parts.append(event.part)
360
+
361
+ partial_message = self._build_partial_response(partial_parts)
362
+ if partial_message is not None:
363
+ state.latest_partial = partial_message
364
+ self._post_partial_message(partial_message, False)
365
+
366
+ elif isinstance(event, PartDeltaEvent):
367
+ index = event.index
368
+ if index >= len(partial_parts):
369
+ logger.warning(
370
+ "Received PartDeltaEvent before corresponding start event",
371
+ extra={"index": index, "current_len": len(partial_parts)},
372
+ )
373
+ continue
374
+
375
+ try:
376
+ updated_part = event.delta.apply(
377
+ cast(ModelResponsePart, partial_parts[index])
378
+ )
379
+ except Exception: # pragma: no cover - defensive logging
380
+ logger.exception(
381
+ "Failed to apply part delta", extra={"event": event}
382
+ )
383
+ continue
384
+
385
+ partial_parts[index] = updated_part
386
+
387
+ partial_message = self._build_partial_response(partial_parts)
388
+ if partial_message is not None:
389
+ state.latest_partial = partial_message
390
+ self._post_partial_message(partial_message, False)
391
+
392
+ elif isinstance(event, FinalResultEvent):
393
+ final_message = (
394
+ state.latest_partial
395
+ or self._build_partial_response(partial_parts)
396
+ )
397
+ self._post_partial_message(final_message, True)
398
+ state.latest_partial = None
399
+ state.final_sent = True
400
+ partial_parts.clear()
401
+ self._stream_state = None
402
+ break
403
+
404
+ # Ignore other AgentStreamEvent variants (e.g. tool call notifications) for partial UI updates.
405
+
406
+ except Exception: # pragma: no cover - defensive logging
407
+ logger.exception(
408
+ "Error while handling agent stream event", extra={"event": event}
409
+ )
410
+
411
+ def _build_partial_response(
412
+ self, parts: list[ModelResponsePart | ToolCallPartDelta]
413
+ ) -> ModelResponse | None:
414
+ """Create a `ModelResponse` from the currently streamed parts."""
415
+
416
+ completed_parts = [
417
+ part for part in parts if not isinstance(part, ToolCallPartDelta)
418
+ ]
419
+ if not completed_parts:
420
+ return None
421
+ return ModelResponse(parts=list(completed_parts))
422
+
423
+ def _post_partial_message(
424
+ self, message: ModelResponse | None, is_last: bool
425
+ ) -> None:
426
+ """Post a partial message to the UI."""
427
+ self.post_message(PartialResponseMessage(message, is_last))
428
+
429
+ def _post_messages_updated(
430
+ self, file_operations: list[FileOperation] | None = None
431
+ ) -> None:
432
+ # Post event to notify listeners of the message history update
433
+ self.post_message(
434
+ MessageHistoryUpdated(
435
+ messages=self.ui_message_history.copy(),
436
+ agent_type=self._current_agent_type,
437
+ file_operations=file_operations,
438
+ )
439
+ )
@@ -347,14 +347,13 @@ async def read_artifact_section(
347
347
 
348
348
  section = service.get_section(artifact_id, mode, section_number)
349
349
 
350
- # Return formatted content with title
351
- formatted_content = f"# {section.title}\n\n{section.content}"
350
+ # Return section content (already contains title header from file storage)
352
351
  logger.debug(
353
352
  "📄 Read section %d with %d characters",
354
353
  section_number,
355
354
  len(section.content),
356
355
  )
357
- return formatted_content
356
+ return section.content
358
357
 
359
358
  except Exception as e:
360
359
  error_msg = (