shotgun-sh 0.1.0.dev17__tar.gz → 0.1.0.dev18__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 (143) hide show
  1. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/PKG-INFO +1 -1
  2. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/pyproject.toml +1 -1
  3. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/agent_manager.py +28 -13
  4. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/user_interaction.py +2 -1
  5. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/manager.py +9 -3
  6. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/research.j2 +1 -1
  7. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/chat.py +17 -1
  8. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/chat_screen/history.py +81 -30
  9. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/.gitignore +0 -0
  10. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/LICENSE +0 -0
  11. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/README.md +0 -0
  12. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/hatch_build.py +0 -0
  13. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/__init__.py +0 -0
  14. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/__init__.py +0 -0
  15. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/artifact_state.py +0 -0
  16. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/common.py +0 -0
  17. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/config/__init__.py +0 -0
  18. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/config/constants.py +0 -0
  19. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/config/manager.py +0 -0
  20. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/config/models.py +0 -0
  21. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/config/provider.py +0 -0
  22. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/__init__.py +0 -0
  23. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/compaction.py +0 -0
  24. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/constants.py +0 -0
  25. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/context_extraction.py +0 -0
  26. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/history_building.py +0 -0
  27. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/history_processors.py +0 -0
  28. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/message_utils.py +0 -0
  29. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/token_counting.py +0 -0
  30. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/history/token_estimation.py +0 -0
  31. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/models.py +0 -0
  32. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/plan.py +0 -0
  33. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/research.py +0 -0
  34. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/specify.py +0 -0
  35. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tasks.py +0 -0
  36. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/__init__.py +0 -0
  37. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/artifact_management.py +0 -0
  38. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
  39. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
  40. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
  41. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
  42. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/models.py +0 -0
  43. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
  44. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
  45. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/file_management.py +0 -0
  46. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
  47. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
  48. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
  49. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/web_search/openai.py +0 -0
  50. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/agents/tools/web_search/utils.py +0 -0
  51. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/__init__.py +0 -0
  52. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/exceptions.py +0 -0
  53. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/manager.py +0 -0
  54. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/models.py +0 -0
  55. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/service.py +0 -0
  56. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/__init__.py +0 -0
  57. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/loader.py +0 -0
  58. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/models.py +0 -0
  59. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/plan/delivery_and_release_plan.yaml +0 -0
  60. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/research/market_research.yaml +0 -0
  61. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/research/sdk_comparison.yaml +0 -0
  62. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/specify/prd.yaml +0 -0
  63. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/templates/specify/product_spec.yaml +0 -0
  64. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/artifacts/utils.py +0 -0
  65. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/build_constants.py +0 -0
  66. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/__init__.py +0 -0
  67. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/codebase/__init__.py +0 -0
  68. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/codebase/commands.py +0 -0
  69. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/codebase/models.py +0 -0
  70. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/config.py +0 -0
  71. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/models.py +0 -0
  72. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/plan.py +0 -0
  73. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/research.py +0 -0
  74. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/specify.py +0 -0
  75. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/tasks.py +0 -0
  76. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/update.py +0 -0
  77. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/cli/utils.py +0 -0
  78. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/__init__.py +0 -0
  79. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/__init__.py +0 -0
  80. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/change_detector.py +0 -0
  81. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/code_retrieval.py +0 -0
  82. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/ingestor.py +0 -0
  83. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/language_config.py +0 -0
  84. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/nl_query.py +0 -0
  85. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/core/parser_loader.py +0 -0
  86. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/models.py +0 -0
  87. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/codebase/service.py +0 -0
  88. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/logging_config.py +0 -0
  89. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/main.py +0 -0
  90. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/posthog_telemetry.py +0 -0
  91. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/__init__.py +0 -0
  92. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/__init__.py +0 -0
  93. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/partials/artifact_system.j2 +0 -0
  94. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
  95. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
  96. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
  97. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
  98. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/plan.j2 +0 -0
  99. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/specify.j2 +0 -0
  100. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/state/artifact_templates_available.j2 +0 -0
  101. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
  102. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/state/existing_artifacts_available.j2 +0 -0
  103. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
  104. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/agents/tasks.j2 +0 -0
  105. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/__init__.py +0 -0
  106. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
  107. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
  108. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
  109. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
  110. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
  111. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
  112. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/history/__init__.py +0 -0
  113. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
  114. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/history/summarization.j2 +0 -0
  115. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/prompts/loader.py +0 -0
  116. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/py.typed +0 -0
  117. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/__init__.py +0 -0
  118. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/artifact_models.py +0 -0
  119. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/artifacts.py +0 -0
  120. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/codebase.py +0 -0
  121. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/exceptions.py +0 -0
  122. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/models.py +0 -0
  123. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sdk/services.py +0 -0
  124. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/sentry_telemetry.py +0 -0
  125. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/telemetry.py +0 -0
  126. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/__init__.py +0 -0
  127. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/app.py +0 -0
  128. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/commands/__init__.py +0 -0
  129. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/components/prompt_input.py +0 -0
  130. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/components/spinner.py +0 -0
  131. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/components/splash.py +0 -0
  132. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/components/vertical_tail.py +0 -0
  133. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/chat.tcss +0 -0
  134. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
  135. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
  136. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/directory_setup.py +0 -0
  137. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/provider_config.py +0 -0
  138. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/screens/splash.py +0 -0
  139. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/tui/styles.tcss +0 -0
  140. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/utils/__init__.py +0 -0
  141. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/utils/env_utils.py +0 -0
  142. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/src/shotgun/utils/file_system_utils.py +0 -0
  143. {shotgun_sh-0.1.0.dev17 → shotgun_sh-0.1.0.dev18}/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.dev17
3
+ Version: 0.1.0.dev18
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.dev17"
3
+ version = "0.1.0.dev18"
4
4
  description = "AI-powered research, planning, and task management CLI tool"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -17,17 +17,23 @@ from pydantic_ai.agent import AgentRunResult
17
17
  from pydantic_ai.messages import (
18
18
  AgentStreamEvent,
19
19
  FinalResultEvent,
20
+ FunctionToolCallEvent,
21
+ FunctionToolResultEvent,
20
22
  ModelMessage,
21
23
  ModelRequest,
22
24
  ModelResponse,
23
25
  ModelResponsePart,
24
26
  PartDeltaEvent,
25
27
  PartStartEvent,
28
+ SystemPromptPart,
29
+ ToolCallPart,
26
30
  ToolCallPartDelta,
27
31
  )
28
32
  from textual.message import Message
29
33
  from textual.widget import Widget
30
34
 
35
+ from shotgun.agents.common import add_system_prompt_message
36
+
31
37
  from .history.compaction import apply_persistent_compaction
32
38
  from .models import AgentDeps, AgentRuntimeOptions, FileOperation
33
39
  from .plan import create_plan_agent
@@ -264,11 +270,6 @@ class AgentManager(Widget):
264
270
  self.ui_message_history.append(ModelRequest.user_text_prompt(prompt))
265
271
  self._post_messages_updated()
266
272
 
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
273
  # Start with persistent message history
273
274
  message_history = self.message_history
274
275
 
@@ -389,19 +390,33 @@ class AgentManager(Widget):
389
390
  state.latest_partial = partial_message
390
391
  self._post_partial_message(partial_message, False)
391
392
 
393
+ elif isinstance(event, FunctionToolCallEvent):
394
+ existing_call_idx = next(
395
+ (
396
+ i
397
+ for i, part in enumerate(partial_parts)
398
+ if isinstance(part, ToolCallPart)
399
+ and part.tool_call_id == event.part.tool_call_id
400
+ ),
401
+ None,
402
+ )
403
+ if existing_call_idx is not None:
404
+ partial_parts[existing_call_idx] = event.part
405
+ else:
406
+ partial_parts.append(event.part)
407
+ partial_message = self._build_partial_response(partial_parts)
408
+ if partial_message is not None:
409
+ state.latest_partial = partial_message
410
+ self._post_partial_message(partial_message, False)
411
+ elif isinstance(event, FunctionToolResultEvent):
412
+ self.ui_message_history.append(ModelRequest(parts=[event.result]))
413
+ self._post_messages_updated() ## this is what the user responded with
392
414
  elif isinstance(event, FinalResultEvent):
393
415
  final_message = (
394
416
  state.latest_partial
395
417
  or self._build_partial_response(partial_parts)
396
418
  )
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.
419
+ self._post_partial_message(final_message, False)
405
420
 
406
421
  except Exception: # pragma: no cover - defensive logging
407
422
  logger.exception(
@@ -13,8 +13,9 @@ logger = get_logger(__name__)
13
13
  async def ask_user(ctx: RunContext[AgentDeps], question: str) -> str:
14
14
  """Ask the human a question and return the answer.
15
15
 
16
+
16
17
  Args:
17
- question: The question to ask the user
18
+ question: The question to ask the user with a clear CTA at the end. Needs to be is readable, clear, and easy to understand. Use Markdown formatting. Make key phrases and words stand out.
18
19
 
19
20
  Returns:
20
21
  The user's response as a string
@@ -27,6 +27,14 @@ from shotgun.logging_config import get_logger
27
27
  logger = get_logger(__name__)
28
28
 
29
29
 
30
+ class CodebaseAlreadyIndexedError(Exception):
31
+ """Raised when a codebase is already indexed."""
32
+
33
+ def __init__(self, repo_path: str):
34
+ self.repo_path = repo_path
35
+ super().__init__(f"Codebase already indexed: {repo_path}")
36
+
37
+
30
38
  class CodebaseFileHandler(FileSystemEventHandler):
31
39
  """Handles file system events for code graph updates."""
32
40
 
@@ -339,9 +347,7 @@ class CodebaseGraphManager:
339
347
 
340
348
  # Check if graph already exists
341
349
  if graph_path.exists():
342
- raise ValueError(
343
- f"Graph already exists for {repo_path}. Use update_graph() to modify it."
344
- )
350
+ raise CodebaseAlreadyIndexedError(repo_path)
345
351
 
346
352
  # Import the builder from local core module
347
353
  from shotgun.codebase.core import CodebaseIngestor
@@ -23,7 +23,7 @@ Use meaningful artifact IDs like: "api-design-patterns", "microservices-study",
23
23
  ## RESEARCH PRINCIPLES
24
24
 
25
25
  {% if interactive_mode -%}
26
- - CRITICAL: BEFORE RUNNING ANY SEARCH TOOL, ASK THE USER FOR CONFIRMATION USING ask_user().
26
+ - CRITICAL: BEFORE RUNNING ANY SEARCH TOOL, ASK THE USER FOR APPROVAL USING ask_user(). FINISH THE QUESTION WITH ASKING FOR A GO AHEAD.
27
27
  {% endif -%}
28
28
  - Build upon existing research rather than starting from scratch
29
29
  - Focus on practical, actionable information over theoretical concepts
@@ -33,6 +33,7 @@ from shotgun.agents.models import (
33
33
  UserAnswer,
34
34
  UserQuestion,
35
35
  )
36
+ from shotgun.codebase.core.manager import CodebaseAlreadyIndexedError
36
37
  from shotgun.sdk.codebase import CodebaseSDK
37
38
  from shotgun.sdk.exceptions import CodebaseNotFoundError, InvalidPathError
38
39
  from shotgun.sdk.services import get_artifact_service, get_codebase_service
@@ -248,6 +249,12 @@ class CodebaseIndexScreen(ModalScreen[CodebaseIndexSelection | None]):
248
249
  disabled=True,
249
250
  )
250
251
 
252
+ def on_mount(self) -> None:
253
+ name_input = self.query_one("#index-codebase-name", Input)
254
+ if not name_input.value and self.selected_path:
255
+ name_input.value = self.selected_path.name
256
+ self._update_confirm()
257
+
251
258
  def _update_confirm(self) -> None:
252
259
  confirm = self.query_one("#index-confirm", Button)
253
260
  name_input = self.query_one("#index-codebase-name", Input)
@@ -475,9 +482,14 @@ class ChatScreen(Screen[None]):
475
482
  if event.is_last:
476
483
  partial_response_widget.partial_response = None
477
484
 
485
+ def _clear_partial_response(self) -> None:
486
+ partial_response_widget = self.query_one(ChatHistory)
487
+ partial_response_widget.partial_response = None
488
+
478
489
  @on(MessageHistoryUpdated)
479
490
  def handle_message_history_updated(self, event: MessageHistoryUpdated) -> None:
480
491
  """Handle message history updates from the agent manager."""
492
+ self._clear_partial_response()
481
493
  self.messages = event.messages
482
494
 
483
495
  # If there are file operations, add a message showing the modified files
@@ -611,6 +623,11 @@ class ChatScreen(Screen[None]):
611
623
  severity="information",
612
624
  timeout=8,
613
625
  )
626
+
627
+ self.mount_hint(codebase_indexed_hint(selection.name))
628
+ except CodebaseAlreadyIndexedError as exc:
629
+ self.notify(str(exc), severity="warning")
630
+ return
614
631
  except InvalidPathError as exc:
615
632
  self.notify(str(exc), severity="error")
616
633
 
@@ -619,7 +636,6 @@ class ChatScreen(Screen[None]):
619
636
  finally:
620
637
  label.update("")
621
638
  label.refresh()
622
- self.mount_hint(codebase_indexed_hint(selection.name))
623
639
 
624
640
  @work
625
641
  async def run_agent(self, message: str) -> None:
@@ -1,14 +1,17 @@
1
1
  import json
2
+ from collections.abc import Sequence
2
3
 
3
4
  from pydantic_ai.messages import (
4
5
  BuiltinToolCallPart,
5
6
  BuiltinToolReturnPart,
6
7
  ModelMessage,
7
8
  ModelRequest,
9
+ ModelRequestPart,
8
10
  ModelResponse,
9
11
  TextPart,
10
12
  ThinkingPart,
11
13
  ToolCallPart,
14
+ ToolReturnPart,
12
15
  )
13
16
  from textual.app import ComposeResult
14
17
  from textual.reactive import reactive
@@ -78,27 +81,32 @@ class ChatHistory(Widget):
78
81
 
79
82
  def compose(self) -> ComposeResult:
80
83
  self.vertical_tail = VerticalTail()
81
- yield self.vertical_tail
82
- yield PartialResponseWidget(self.partial_response).data_bind(
83
- item=ChatHistory.partial_response
84
- )
84
+ with self.vertical_tail:
85
+ for item in self.items:
86
+ if isinstance(item, ModelRequest):
87
+ yield UserQuestionWidget(item)
88
+ elif isinstance(item, ModelResponse):
89
+ yield AgentResponseWidget(item)
90
+ yield PartialResponseWidget(self.partial_response).data_bind(
91
+ item=ChatHistory.partial_response
92
+ )
93
+
94
+ def watch_partial_response(self, _partial_response: ModelMessage | None) -> None:
95
+ self.call_after_refresh(self.autoscroll)
85
96
 
86
97
  def update_messages(self, messages: list[ModelMessage]) -> None:
87
98
  """Update the displayed messages without recomposing."""
88
99
  if not self.vertical_tail:
89
100
  return
90
101
 
91
- # Clear existing widgets
92
- self.vertical_tail.remove_children()
102
+ self.items = messages
103
+ self.refresh(recompose=True)
93
104
 
94
- # Add new message widgets
95
- for item in messages:
96
- if isinstance(item, ModelRequest):
97
- self.vertical_tail.mount(UserQuestionWidget(item))
98
- elif isinstance(item, ModelResponse):
99
- self.vertical_tail.mount(AgentResponseWidget(item))
105
+ self.autoscroll()
100
106
 
101
- self.items = messages
107
+ def autoscroll(self) -> None:
108
+ if self.vertical_tail:
109
+ self.vertical_tail.scroll_end(animate=False)
102
110
 
103
111
 
104
112
  class UserQuestionWidget(Widget):
@@ -116,6 +124,20 @@ class UserQuestionWidget(Widget):
116
124
  )
117
125
  yield Markdown(markdown=f"**>** {prompt}")
118
126
 
127
+ def format_prompt_parts(self, parts: Sequence[ModelRequestPart]) -> str:
128
+ acc = ""
129
+ for part in parts:
130
+ if isinstance(part, TextPart):
131
+ acc += (
132
+ f"**>** {part.content if isinstance(part.content, str) else ''}\n\n"
133
+ )
134
+ elif isinstance(part, ToolCallPart):
135
+ if part.tool_name == "ask_user" and isinstance(part.content, dict):
136
+ acc += f"**>** {part.content['answer']}\n\n"
137
+ else:
138
+ acc += "∟ finished\n\n" # let's not show anything yet
139
+ return acc
140
+
119
141
 
120
142
  class AgentResponseWidget(Widget):
121
143
  def __init__(self, item: ModelResponse | None) -> None:
@@ -133,34 +155,38 @@ class AgentResponseWidget(Widget):
133
155
  acc = ""
134
156
  if self.item is None:
135
157
  return ""
136
- for part in self.item.parts: # TextPart | ToolCallPart | BuiltinToolCallPart | BuiltinToolReturnPart | ThinkingPart
158
+ for idx, part in enumerate(self.item.parts):
137
159
  if isinstance(part, TextPart):
138
160
  acc += part.content + "\n\n"
139
161
  elif isinstance(part, ToolCallPart):
140
162
  parts_str = self._format_tool_call_part(part)
141
163
  acc += parts_str + "\n\n"
164
+ elif isinstance(part, ToolReturnPart):
165
+ acc += (
166
+ f"tool ({part.tool_name}) return: "
167
+ + self._format_tool_return_call_part(part)
168
+ + "\n\n"
169
+ )
142
170
  elif isinstance(part, BuiltinToolCallPart):
143
- acc += f"{part.tool_name}({part.args})\n\n"
171
+ acc += f"builtin tool ({part.tool_name}): {part.args}\n\n"
144
172
  elif isinstance(part, BuiltinToolReturnPart):
145
- acc += f"{part.tool_name}()\n\n"
173
+ acc += f"builtin tool ({part.tool_name}) return: {part.content}\n\n"
146
174
  elif isinstance(part, ThinkingPart):
147
- acc += f"{part.content}\n\n"
175
+ if (
176
+ idx == len(self.item.parts) - 1
177
+ ): # show the thinking part only if it's the last part
178
+ acc += (
179
+ f"thinking: {part.content}\n\n"
180
+ if part.content
181
+ else "Thinking..."
182
+ )
183
+ else:
184
+ continue
148
185
  return acc.strip()
149
186
 
150
187
  def _format_tool_call_part(self, part: ToolCallPart) -> str:
151
188
  if part.tool_name == "ask_user":
152
- if isinstance(part.args, str):
153
- try:
154
- _args = json.loads(part.args) if part.args.strip() else {}
155
- except json.JSONDecodeError:
156
- _args = {}
157
- else:
158
- _args = part.args
159
-
160
- if isinstance(_args, dict) and "question" in _args:
161
- return f"{_args['question']}"
162
- else:
163
- return "❓ "
189
+ return self._format_ask_user_part(part)
164
190
  if part.tool_name == "write_artifact_section":
165
191
  if isinstance(part.args, dict) and "section_title" in part.args:
166
192
  return f"{part.tool_name}({part.args['section_title']})"
@@ -170,6 +196,31 @@ class AgentResponseWidget(Widget):
170
196
  if isinstance(part.args, dict) and "name" in part.args:
171
197
  return f"{part.tool_name}({part.args['name']})"
172
198
  else:
173
- return f"{part.tool_name}()"
199
+ return f"{part.tool_name}()"
174
200
 
175
201
  return f"{part.tool_name}({part.args})"
202
+
203
+ def _format_ask_user_part(
204
+ self,
205
+ part: ToolCallPart,
206
+ ) -> str:
207
+ return "*Answer to continue*"
208
+ if isinstance(part.args, str):
209
+ try:
210
+ _args = json.loads(part.args) if part.args.strip() else {}
211
+ except json.JSONDecodeError:
212
+ _args = {}
213
+ else:
214
+ _args = part.args
215
+
216
+ if isinstance(_args, dict) and "question" in _args:
217
+ return f"{_args['question']}"
218
+ else:
219
+ return "❓ "
220
+
221
+ def _format_tool_return_call_part(self, part: ToolReturnPart) -> str:
222
+ content = part.content
223
+ if part.tool_name == "ask_user":
224
+ response = content.get("answer", "") if isinstance(content, dict) else ""
225
+ return f"**⏺** {response}"
226
+ return f"∟ {content}"