shotgun-sh 0.2.7.dev1__tar.gz → 0.2.7.dev2__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.
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/PKG-INFO +2 -1
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/pyproject.toml +2 -1
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/agent_manager.py +191 -13
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/common.py +42 -17
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/file_management.py +55 -9
- shotgun_sh-0.2.7.dev2/src/shotgun/prompts/agents/specify.j2 +318 -0
- shotgun_sh-0.2.7.dev1/src/shotgun/prompts/agents/specify.j2 +0 -51
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/.gitignore +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/LICENSE +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/README.md +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/README_PYPI.md +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/hatch_build.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/base.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/openai.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_counting/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/llm.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/agents/usage_manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/api_endpoints.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/build_constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/feedback.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/ingestor.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/llm_proxy/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/llm_proxy/clients.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/llm_proxy/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/main.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/prompts/tools/web_search.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/client.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/shotgun_web/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/app.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/feedback.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/model_picker.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/screens/welcome.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/datetime_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.7.dev2}/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.2.7.
|
|
3
|
+
Version: 0.2.7.dev2
|
|
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
|
|
@@ -34,6 +34,7 @@ Requires-Dist: pydantic-ai>=0.0.14
|
|
|
34
34
|
Requires-Dist: rich>=13.0.0
|
|
35
35
|
Requires-Dist: sentencepiece>=0.2.0
|
|
36
36
|
Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
|
|
37
|
+
Requires-Dist: tenacity>=8.0.0
|
|
37
38
|
Requires-Dist: textual-dev>=1.7.0
|
|
38
39
|
Requires-Dist: textual>=6.1.0
|
|
39
40
|
Requires-Dist: tiktoken>=0.7.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "shotgun-sh"
|
|
3
|
-
version = "0.2.7.
|
|
3
|
+
version = "0.2.7.dev2"
|
|
4
4
|
description = "AI-powered research, planning, and task management CLI tool"
|
|
5
5
|
readme = "README_PYPI.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -46,6 +46,7 @@ dependencies = [
|
|
|
46
46
|
"sentencepiece>=0.2.0",
|
|
47
47
|
"packaging>=23.0",
|
|
48
48
|
"genai-prices>=0.0.27",
|
|
49
|
+
"tenacity>=8.0.0",
|
|
49
50
|
]
|
|
50
51
|
|
|
51
52
|
[project.urls]
|
|
@@ -4,9 +4,17 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
from collections.abc import AsyncIterable, Sequence
|
|
6
6
|
from dataclasses import dataclass, field, is_dataclass, replace
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
8
9
|
|
|
9
10
|
import logfire
|
|
11
|
+
from tenacity import (
|
|
12
|
+
before_sleep_log,
|
|
13
|
+
retry,
|
|
14
|
+
retry_if_exception,
|
|
15
|
+
stop_after_attempt,
|
|
16
|
+
wait_exponential,
|
|
17
|
+
)
|
|
10
18
|
|
|
11
19
|
if TYPE_CHECKING:
|
|
12
20
|
from shotgun.agents.conversation_history import ConversationState
|
|
@@ -55,6 +63,35 @@ from .tasks import create_tasks_agent
|
|
|
55
63
|
logger = logging.getLogger(__name__)
|
|
56
64
|
|
|
57
65
|
|
|
66
|
+
def _is_retryable_error(exception: BaseException) -> bool:
|
|
67
|
+
"""Check if exception should trigger a retry.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
exception: The exception to check.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if the exception is a transient error that should be retried.
|
|
74
|
+
"""
|
|
75
|
+
# ValueError for truncated/incomplete JSON
|
|
76
|
+
if isinstance(exception, ValueError):
|
|
77
|
+
error_str = str(exception)
|
|
78
|
+
return "EOF while parsing" in error_str or (
|
|
79
|
+
"JSON" in error_str and "parsing" in error_str
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# API errors (overload, rate limits)
|
|
83
|
+
exception_name = type(exception).__name__
|
|
84
|
+
if "APIStatusError" in exception_name:
|
|
85
|
+
error_str = str(exception)
|
|
86
|
+
return "overload" in error_str.lower() or "rate" in error_str.lower()
|
|
87
|
+
|
|
88
|
+
# Network errors
|
|
89
|
+
if "ConnectionError" in exception_name or "TimeoutError" in exception_name:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
58
95
|
class MessageHistoryUpdated(Message):
|
|
59
96
|
"""Event posted when the message history is updated."""
|
|
60
97
|
|
|
@@ -268,6 +305,49 @@ class AgentManager(Widget):
|
|
|
268
305
|
f"Invalid agent type: {agent_type}. Must be one of: {', '.join(e.value for e in AgentType)}"
|
|
269
306
|
) from None
|
|
270
307
|
|
|
308
|
+
@retry(
|
|
309
|
+
stop=stop_after_attempt(3),
|
|
310
|
+
wait=wait_exponential(multiplier=1, min=1, max=8),
|
|
311
|
+
retry=retry_if_exception(_is_retryable_error),
|
|
312
|
+
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
313
|
+
reraise=True,
|
|
314
|
+
)
|
|
315
|
+
async def _run_agent_with_retry(
|
|
316
|
+
self,
|
|
317
|
+
agent: Agent[AgentDeps, AgentResponse],
|
|
318
|
+
prompt: str | None,
|
|
319
|
+
deps: AgentDeps,
|
|
320
|
+
usage_limits: UsageLimits | None,
|
|
321
|
+
message_history: list[ModelMessage],
|
|
322
|
+
event_stream_handler: Any,
|
|
323
|
+
**kwargs: Any,
|
|
324
|
+
) -> AgentRunResult[AgentResponse]:
|
|
325
|
+
"""Run agent with automatic retry on transient errors.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
agent: The agent to run.
|
|
329
|
+
prompt: Optional prompt to send to the agent.
|
|
330
|
+
deps: Agent dependencies.
|
|
331
|
+
usage_limits: Optional usage limits.
|
|
332
|
+
message_history: Message history to provide to agent.
|
|
333
|
+
event_stream_handler: Event handler for streaming.
|
|
334
|
+
**kwargs: Additional keyword arguments.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
The agent run result.
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
Various exceptions if all retries fail.
|
|
341
|
+
"""
|
|
342
|
+
return await agent.run(
|
|
343
|
+
prompt,
|
|
344
|
+
deps=deps,
|
|
345
|
+
usage_limits=usage_limits,
|
|
346
|
+
message_history=message_history,
|
|
347
|
+
event_stream_handler=event_stream_handler,
|
|
348
|
+
**kwargs,
|
|
349
|
+
)
|
|
350
|
+
|
|
271
351
|
async def run(
|
|
272
352
|
self,
|
|
273
353
|
prompt: str | None = None,
|
|
@@ -394,8 +474,9 @@ class AgentManager(Widget):
|
|
|
394
474
|
)
|
|
395
475
|
|
|
396
476
|
try:
|
|
397
|
-
result: AgentRunResult[AgentResponse] = await self.
|
|
398
|
-
|
|
477
|
+
result: AgentRunResult[AgentResponse] = await self._run_agent_with_retry(
|
|
478
|
+
agent=self.current_agent,
|
|
479
|
+
prompt=prompt,
|
|
399
480
|
deps=deps,
|
|
400
481
|
usage_limits=usage_limits,
|
|
401
482
|
message_history=message_history,
|
|
@@ -404,6 +485,36 @@ class AgentManager(Widget):
|
|
|
404
485
|
else None,
|
|
405
486
|
**kwargs,
|
|
406
487
|
)
|
|
488
|
+
except ValueError as e:
|
|
489
|
+
# Handle truncated/incomplete JSON in tool calls specifically
|
|
490
|
+
error_str = str(e)
|
|
491
|
+
if "EOF while parsing" in error_str or (
|
|
492
|
+
"JSON" in error_str and "parsing" in error_str
|
|
493
|
+
):
|
|
494
|
+
logger.error(
|
|
495
|
+
"Tool call with truncated/incomplete JSON arguments detected",
|
|
496
|
+
extra={
|
|
497
|
+
"agent_mode": self._current_agent_type.value,
|
|
498
|
+
"model_name": model_name,
|
|
499
|
+
"error": error_str,
|
|
500
|
+
},
|
|
501
|
+
)
|
|
502
|
+
logfire.error(
|
|
503
|
+
"Tool call with truncated JSON arguments",
|
|
504
|
+
agent_mode=self._current_agent_type.value,
|
|
505
|
+
model_name=model_name,
|
|
506
|
+
error=error_str,
|
|
507
|
+
)
|
|
508
|
+
# Add helpful hint message for the user
|
|
509
|
+
self.ui_message_history.append(
|
|
510
|
+
HintMessage(
|
|
511
|
+
message="⚠️ The agent attempted an operation with arguments that were too large (truncated JSON). "
|
|
512
|
+
"Try breaking your request into smaller steps or more focused contracts."
|
|
513
|
+
)
|
|
514
|
+
)
|
|
515
|
+
self._post_messages_updated()
|
|
516
|
+
# Re-raise to maintain error visibility
|
|
517
|
+
raise
|
|
407
518
|
except Exception as e:
|
|
408
519
|
# Log the error with full stack trace to shotgun.log and Logfire
|
|
409
520
|
logger.exception(
|
|
@@ -427,13 +538,40 @@ class AgentManager(Widget):
|
|
|
427
538
|
|
|
428
539
|
# Agent ALWAYS returns AgentResponse with structured output
|
|
429
540
|
agent_response = result.output
|
|
430
|
-
logger.debug(
|
|
541
|
+
logger.debug(
|
|
542
|
+
"Agent returned structured AgentResponse",
|
|
543
|
+
extra={
|
|
544
|
+
"has_response": agent_response.response is not None,
|
|
545
|
+
"response_length": len(agent_response.response)
|
|
546
|
+
if agent_response.response
|
|
547
|
+
else 0,
|
|
548
|
+
"response_preview": agent_response.response[:100] + "..."
|
|
549
|
+
if agent_response.response and len(agent_response.response) > 100
|
|
550
|
+
else agent_response.response or "(empty)",
|
|
551
|
+
"has_clarifying_questions": bool(agent_response.clarifying_questions),
|
|
552
|
+
"num_clarifying_questions": len(agent_response.clarifying_questions)
|
|
553
|
+
if agent_response.clarifying_questions
|
|
554
|
+
else 0,
|
|
555
|
+
},
|
|
556
|
+
)
|
|
431
557
|
|
|
432
558
|
# Always add the agent's response messages to maintain conversation history
|
|
433
559
|
self.ui_message_history = original_messages + cast(
|
|
434
560
|
list[ModelRequest | ModelResponse | HintMessage], result.new_messages()
|
|
435
561
|
)
|
|
436
562
|
|
|
563
|
+
# Get file operations early so we can use them for contextual messages
|
|
564
|
+
file_operations = deps.file_tracker.operations.copy()
|
|
565
|
+
self.recently_change_files = file_operations
|
|
566
|
+
|
|
567
|
+
logger.debug(
|
|
568
|
+
"File operations tracked",
|
|
569
|
+
extra={
|
|
570
|
+
"num_file_operations": len(file_operations),
|
|
571
|
+
"operation_files": [Path(op.file_path).name for op in file_operations],
|
|
572
|
+
},
|
|
573
|
+
)
|
|
574
|
+
|
|
437
575
|
# Check if there are clarifying questions
|
|
438
576
|
if agent_response.clarifying_questions:
|
|
439
577
|
logger.info(
|
|
@@ -480,12 +618,50 @@ class AgentManager(Widget):
|
|
|
480
618
|
response_text=agent_response.response,
|
|
481
619
|
)
|
|
482
620
|
)
|
|
621
|
+
|
|
622
|
+
# Post UI update with hint messages and file operations
|
|
623
|
+
logger.debug(
|
|
624
|
+
"Posting UI update for Q&A mode with hint messages and file operations"
|
|
625
|
+
)
|
|
626
|
+
self._post_messages_updated(file_operations)
|
|
483
627
|
else:
|
|
484
|
-
# No clarifying questions -
|
|
628
|
+
# No clarifying questions - show the response or a default success message
|
|
485
629
|
if agent_response.response and agent_response.response.strip():
|
|
630
|
+
logger.debug(
|
|
631
|
+
"Adding agent response as hint",
|
|
632
|
+
extra={
|
|
633
|
+
"response_preview": agent_response.response[:100] + "..."
|
|
634
|
+
if len(agent_response.response) > 100
|
|
635
|
+
else agent_response.response,
|
|
636
|
+
"has_file_operations": len(file_operations) > 0,
|
|
637
|
+
},
|
|
638
|
+
)
|
|
486
639
|
self.ui_message_history.append(
|
|
487
640
|
HintMessage(message=agent_response.response)
|
|
488
641
|
)
|
|
642
|
+
else:
|
|
643
|
+
# Fallback: response is empty or whitespace
|
|
644
|
+
logger.debug(
|
|
645
|
+
"Agent response was empty, using fallback completion message",
|
|
646
|
+
extra={"has_file_operations": len(file_operations) > 0},
|
|
647
|
+
)
|
|
648
|
+
# Show contextual message based on whether files were modified
|
|
649
|
+
if file_operations:
|
|
650
|
+
self.ui_message_history.append(
|
|
651
|
+
HintMessage(
|
|
652
|
+
message="✅ Task completed - files have been modified"
|
|
653
|
+
)
|
|
654
|
+
)
|
|
655
|
+
else:
|
|
656
|
+
self.ui_message_history.append(
|
|
657
|
+
HintMessage(message="✅ Task completed")
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# Post UI update immediately so user sees the response without delay
|
|
661
|
+
logger.debug(
|
|
662
|
+
"Posting immediate UI update with hint message and file operations"
|
|
663
|
+
)
|
|
664
|
+
self._post_messages_updated(file_operations)
|
|
489
665
|
|
|
490
666
|
# Apply compaction to persistent message history to prevent cascading growth
|
|
491
667
|
all_messages = result.all_messages()
|
|
@@ -517,16 +693,18 @@ class AgentManager(Widget):
|
|
|
517
693
|
self.message_history = all_messages
|
|
518
694
|
|
|
519
695
|
usage = result.usage()
|
|
520
|
-
deps.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
696
|
+
if hasattr(deps, "llm_model") and deps.llm_model is not None:
|
|
697
|
+
deps.usage_manager.add_usage(
|
|
698
|
+
usage, model_name=deps.llm_model.name, provider=deps.llm_model.provider
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
logger.warning(
|
|
702
|
+
"llm_model is None, skipping usage tracking",
|
|
703
|
+
extra={"agent_mode": self._current_agent_type.value},
|
|
704
|
+
)
|
|
527
705
|
|
|
528
|
-
#
|
|
529
|
-
|
|
706
|
+
# UI updates are now posted immediately in each branch (Q&A or non-Q&A)
|
|
707
|
+
# before compaction, so no duplicate posting needed here
|
|
530
708
|
|
|
531
709
|
return result
|
|
532
710
|
|
|
@@ -384,23 +384,48 @@ def get_agent_existing_files(agent_mode: AgentType | None = None) -> list[str]:
|
|
|
384
384
|
relative_path = file_path.relative_to(base_path)
|
|
385
385
|
existing_files.append(str(relative_path))
|
|
386
386
|
else:
|
|
387
|
-
# For other agents, check
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
#
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
#
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
387
|
+
# For other agents, check files/directories they have access to
|
|
388
|
+
allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
|
|
389
|
+
|
|
390
|
+
# Convert single Path/string to list of Paths for uniform handling
|
|
391
|
+
if isinstance(allowed_paths_raw, str):
|
|
392
|
+
# Special case: "*" means export agent (shouldn't reach here but handle it)
|
|
393
|
+
allowed_paths = (
|
|
394
|
+
[Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
|
|
395
|
+
)
|
|
396
|
+
elif isinstance(allowed_paths_raw, Path):
|
|
397
|
+
allowed_paths = [allowed_paths_raw]
|
|
398
|
+
else:
|
|
399
|
+
# Already a list
|
|
400
|
+
allowed_paths = allowed_paths_raw
|
|
401
|
+
|
|
402
|
+
# Check each allowed path
|
|
403
|
+
for allowed_path in allowed_paths:
|
|
404
|
+
allowed_str = str(allowed_path)
|
|
405
|
+
|
|
406
|
+
# Check if it's a directory (no .md suffix)
|
|
407
|
+
if not allowed_path.suffix or not allowed_str.endswith(".md"):
|
|
408
|
+
# It's a directory - list all files within it
|
|
409
|
+
dir_path = base_path / allowed_str
|
|
410
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
411
|
+
for file_path in dir_path.rglob("*"):
|
|
412
|
+
if file_path.is_file():
|
|
413
|
+
relative_path = file_path.relative_to(base_path)
|
|
414
|
+
existing_files.append(str(relative_path))
|
|
415
|
+
else:
|
|
416
|
+
# It's a file - check if it exists
|
|
417
|
+
file_path = base_path / allowed_str
|
|
418
|
+
if file_path.exists():
|
|
419
|
+
existing_files.append(allowed_str)
|
|
420
|
+
|
|
421
|
+
# Also check for associated directory (e.g., research/ for research.md)
|
|
422
|
+
base_name = allowed_str.replace(".md", "")
|
|
423
|
+
dir_path = base_path / base_name
|
|
424
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
425
|
+
for file_path in dir_path.rglob("*"):
|
|
426
|
+
if file_path.is_file():
|
|
427
|
+
relative_path = file_path.relative_to(base_path)
|
|
428
|
+
existing_files.append(str(relative_path))
|
|
404
429
|
|
|
405
430
|
return existing_files
|
|
406
431
|
|
|
@@ -15,11 +15,18 @@ from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
|
15
15
|
logger = get_logger(__name__)
|
|
16
16
|
|
|
17
17
|
# Map agent modes to their allowed directories/files (in workflow order)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
# Values can be:
|
|
19
|
+
# - A Path: exact file (e.g., Path("research.md"))
|
|
20
|
+
# - A list of Paths: multiple allowed files/directories (e.g., [Path("specification.md"), Path("contracts")])
|
|
21
|
+
# - "*": any file except protected files (for export agent)
|
|
22
|
+
AGENT_DIRECTORIES: dict[AgentType, str | Path | list[Path]] = {
|
|
23
|
+
AgentType.RESEARCH: Path("research.md"),
|
|
24
|
+
AgentType.SPECIFY: [
|
|
25
|
+
Path("specification.md"),
|
|
26
|
+
Path("contracts"),
|
|
27
|
+
], # Specify can write specs and contract files
|
|
28
|
+
AgentType.PLAN: Path("plan.md"),
|
|
29
|
+
AgentType.TASKS: Path("tasks.md"),
|
|
23
30
|
AgentType.EXPORT: "*", # Export agent can write anywhere except protected files
|
|
24
31
|
}
|
|
25
32
|
|
|
@@ -60,13 +67,52 @@ def _validate_agent_scoped_path(filename: str, agent_mode: AgentType | None) ->
|
|
|
60
67
|
# Allow writing anywhere else in .shotgun directory
|
|
61
68
|
full_path = (base_path / filename).resolve()
|
|
62
69
|
else:
|
|
63
|
-
# For other agents,
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
# For other agents, check if they have access to the requested file
|
|
71
|
+
allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
|
|
72
|
+
|
|
73
|
+
# Convert single Path/string to list of Paths for uniform handling
|
|
74
|
+
if isinstance(allowed_paths_raw, str):
|
|
75
|
+
# Special case: "*" means export agent
|
|
76
|
+
allowed_paths = (
|
|
77
|
+
[Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
|
|
78
|
+
)
|
|
79
|
+
elif isinstance(allowed_paths_raw, Path):
|
|
80
|
+
allowed_paths = [allowed_paths_raw]
|
|
81
|
+
else:
|
|
82
|
+
# Already a list
|
|
83
|
+
allowed_paths = allowed_paths_raw
|
|
84
|
+
|
|
85
|
+
# Check if filename matches any allowed path
|
|
86
|
+
is_allowed = False
|
|
87
|
+
for allowed_path in allowed_paths:
|
|
88
|
+
allowed_str = str(allowed_path)
|
|
89
|
+
|
|
90
|
+
# Check if it's a directory (no .md extension or suffix)
|
|
91
|
+
# Directories: Path("contracts") has no suffix, files: Path("spec.md") has .md suffix
|
|
92
|
+
if not allowed_path.suffix or (
|
|
93
|
+
allowed_path.suffix and not allowed_str.endswith(".md")
|
|
94
|
+
):
|
|
95
|
+
# Directory - allow any file within this directory
|
|
96
|
+
# Check both "contracts/file.py" and "contracts" prefix
|
|
97
|
+
if (
|
|
98
|
+
filename.startswith(allowed_str + "/")
|
|
99
|
+
or filename == allowed_str
|
|
100
|
+
):
|
|
101
|
+
is_allowed = True
|
|
102
|
+
break
|
|
103
|
+
else:
|
|
104
|
+
# Exact file match
|
|
105
|
+
if filename == allowed_str:
|
|
106
|
+
is_allowed = True
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if not is_allowed:
|
|
110
|
+
allowed_display = ", ".join(f"'{p}'" for p in allowed_paths)
|
|
66
111
|
raise ValueError(
|
|
67
|
-
f"{agent_mode.value.capitalize()} agent can only write to
|
|
112
|
+
f"{agent_mode.value.capitalize()} agent can only write to {allowed_display}. "
|
|
68
113
|
f"Attempted to write to '{filename}'"
|
|
69
114
|
)
|
|
115
|
+
|
|
70
116
|
full_path = (base_path / filename).resolve()
|
|
71
117
|
else:
|
|
72
118
|
# No agent mode specified, fall back to old validation
|