shotgun-sh 0.2.7.dev1__tar.gz → 0.2.8__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.8}/PKG-INFO +2 -1
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/pyproject.toml +2 -1
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/agent_manager.py +222 -20
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/common.py +42 -17
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/file_management.py +55 -9
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/build_constants.py +2 -2
- shotgun_sh-0.2.8/src/shotgun/prompts/agents/specify.j2 +318 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/chat.py +6 -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.8}/.gitignore +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/LICENSE +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/README.md +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/README_PYPI.md +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/hatch_build.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/base.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/openai.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_counting/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/llm.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/agents/usage_manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/api_endpoints.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/feedback.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/ingestor.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/llm_proxy/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/llm_proxy/clients.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/llm_proxy/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/main.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/prompts/tools/web_search.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/shotgun_web/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/shotgun_web/client.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/shotgun_web/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/shotgun_web/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/app.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/feedback.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/model_picker.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/screens/welcome.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/utils/datetime_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8}/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.
|
|
3
|
+
Version: 0.2.8
|
|
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.
|
|
3
|
+
version = "0.2.8"
|
|
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
|
|
@@ -32,6 +40,7 @@ from pydantic_ai.messages import (
|
|
|
32
40
|
SystemPromptPart,
|
|
33
41
|
ToolCallPart,
|
|
34
42
|
ToolCallPartDelta,
|
|
43
|
+
UserPromptPart,
|
|
35
44
|
)
|
|
36
45
|
from textual.message import Message
|
|
37
46
|
from textual.widget import Widget
|
|
@@ -55,6 +64,35 @@ from .tasks import create_tasks_agent
|
|
|
55
64
|
logger = logging.getLogger(__name__)
|
|
56
65
|
|
|
57
66
|
|
|
67
|
+
def _is_retryable_error(exception: BaseException) -> bool:
|
|
68
|
+
"""Check if exception should trigger a retry.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
exception: The exception to check.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if the exception is a transient error that should be retried.
|
|
75
|
+
"""
|
|
76
|
+
# ValueError for truncated/incomplete JSON
|
|
77
|
+
if isinstance(exception, ValueError):
|
|
78
|
+
error_str = str(exception)
|
|
79
|
+
return "EOF while parsing" in error_str or (
|
|
80
|
+
"JSON" in error_str and "parsing" in error_str
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# API errors (overload, rate limits)
|
|
84
|
+
exception_name = type(exception).__name__
|
|
85
|
+
if "APIStatusError" in exception_name:
|
|
86
|
+
error_str = str(exception)
|
|
87
|
+
return "overload" in error_str.lower() or "rate" in error_str.lower()
|
|
88
|
+
|
|
89
|
+
# Network errors
|
|
90
|
+
if "ConnectionError" in exception_name or "TimeoutError" in exception_name:
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
58
96
|
class MessageHistoryUpdated(Message):
|
|
59
97
|
"""Event posted when the message history is updated."""
|
|
60
98
|
|
|
@@ -268,6 +306,49 @@ class AgentManager(Widget):
|
|
|
268
306
|
f"Invalid agent type: {agent_type}. Must be one of: {', '.join(e.value for e in AgentType)}"
|
|
269
307
|
) from None
|
|
270
308
|
|
|
309
|
+
@retry(
|
|
310
|
+
stop=stop_after_attempt(3),
|
|
311
|
+
wait=wait_exponential(multiplier=1, min=1, max=8),
|
|
312
|
+
retry=retry_if_exception(_is_retryable_error),
|
|
313
|
+
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
314
|
+
reraise=True,
|
|
315
|
+
)
|
|
316
|
+
async def _run_agent_with_retry(
|
|
317
|
+
self,
|
|
318
|
+
agent: Agent[AgentDeps, AgentResponse],
|
|
319
|
+
prompt: str | None,
|
|
320
|
+
deps: AgentDeps,
|
|
321
|
+
usage_limits: UsageLimits | None,
|
|
322
|
+
message_history: list[ModelMessage],
|
|
323
|
+
event_stream_handler: Any,
|
|
324
|
+
**kwargs: Any,
|
|
325
|
+
) -> AgentRunResult[AgentResponse]:
|
|
326
|
+
"""Run agent with automatic retry on transient errors.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
agent: The agent to run.
|
|
330
|
+
prompt: Optional prompt to send to the agent.
|
|
331
|
+
deps: Agent dependencies.
|
|
332
|
+
usage_limits: Optional usage limits.
|
|
333
|
+
message_history: Message history to provide to agent.
|
|
334
|
+
event_stream_handler: Event handler for streaming.
|
|
335
|
+
**kwargs: Additional keyword arguments.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
The agent run result.
|
|
339
|
+
|
|
340
|
+
Raises:
|
|
341
|
+
Various exceptions if all retries fail.
|
|
342
|
+
"""
|
|
343
|
+
return await agent.run(
|
|
344
|
+
prompt,
|
|
345
|
+
deps=deps,
|
|
346
|
+
usage_limits=usage_limits,
|
|
347
|
+
message_history=message_history,
|
|
348
|
+
event_stream_handler=event_stream_handler,
|
|
349
|
+
**kwargs,
|
|
350
|
+
)
|
|
351
|
+
|
|
271
352
|
async def run(
|
|
272
353
|
self,
|
|
273
354
|
prompt: str | None = None,
|
|
@@ -301,13 +382,12 @@ class AgentManager(Widget):
|
|
|
301
382
|
|
|
302
383
|
# Clear file tracker before each run to track only this run's operations
|
|
303
384
|
deps.file_tracker.clear()
|
|
304
|
-
# preprocess messages; maybe we need to include the user answer in the message history
|
|
305
385
|
|
|
306
|
-
|
|
386
|
+
# Don't manually add the user prompt - Pydantic AI will include it in result.new_messages()
|
|
387
|
+
# This prevents duplicates and confusion with incremental mounting
|
|
307
388
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
self._post_messages_updated()
|
|
389
|
+
# Save current message history before the run
|
|
390
|
+
original_messages = self.ui_message_history.copy()
|
|
311
391
|
|
|
312
392
|
# Start with persistent message history
|
|
313
393
|
message_history = self.message_history
|
|
@@ -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,64 @@ 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
|
-
#
|
|
433
|
-
|
|
558
|
+
# Merge agent's response messages, avoiding duplicates
|
|
559
|
+
# The TUI may have already added the user prompt, so check for it
|
|
560
|
+
new_messages = cast(
|
|
434
561
|
list[ModelRequest | ModelResponse | HintMessage], result.new_messages()
|
|
435
562
|
)
|
|
436
563
|
|
|
564
|
+
# Deduplicate: skip user prompts that are already in original_messages
|
|
565
|
+
deduplicated_new_messages = []
|
|
566
|
+
for msg in new_messages:
|
|
567
|
+
# Check if this is a user prompt that's already in original_messages
|
|
568
|
+
if isinstance(msg, ModelRequest) and any(
|
|
569
|
+
isinstance(part, UserPromptPart) for part in msg.parts
|
|
570
|
+
):
|
|
571
|
+
# Check if an identical user prompt is already in original_messages
|
|
572
|
+
already_exists = any(
|
|
573
|
+
isinstance(existing, ModelRequest)
|
|
574
|
+
and any(isinstance(p, UserPromptPart) for p in existing.parts)
|
|
575
|
+
and existing.parts == msg.parts
|
|
576
|
+
for existing in original_messages[
|
|
577
|
+
-5:
|
|
578
|
+
] # Check last 5 messages for efficiency
|
|
579
|
+
)
|
|
580
|
+
if already_exists:
|
|
581
|
+
continue # Skip this duplicate user prompt
|
|
582
|
+
|
|
583
|
+
deduplicated_new_messages.append(msg)
|
|
584
|
+
|
|
585
|
+
self.ui_message_history = original_messages + deduplicated_new_messages
|
|
586
|
+
|
|
587
|
+
# Get file operations early so we can use them for contextual messages
|
|
588
|
+
file_operations = deps.file_tracker.operations.copy()
|
|
589
|
+
self.recently_change_files = file_operations
|
|
590
|
+
|
|
591
|
+
logger.debug(
|
|
592
|
+
"File operations tracked",
|
|
593
|
+
extra={
|
|
594
|
+
"num_file_operations": len(file_operations),
|
|
595
|
+
"operation_files": [Path(op.file_path).name for op in file_operations],
|
|
596
|
+
},
|
|
597
|
+
)
|
|
598
|
+
|
|
437
599
|
# Check if there are clarifying questions
|
|
438
600
|
if agent_response.clarifying_questions:
|
|
439
601
|
logger.info(
|
|
@@ -480,12 +642,50 @@ class AgentManager(Widget):
|
|
|
480
642
|
response_text=agent_response.response,
|
|
481
643
|
)
|
|
482
644
|
)
|
|
645
|
+
|
|
646
|
+
# Post UI update with hint messages and file operations
|
|
647
|
+
logger.debug(
|
|
648
|
+
"Posting UI update for Q&A mode with hint messages and file operations"
|
|
649
|
+
)
|
|
650
|
+
self._post_messages_updated(file_operations)
|
|
483
651
|
else:
|
|
484
|
-
# No clarifying questions -
|
|
652
|
+
# No clarifying questions - show the response or a default success message
|
|
485
653
|
if agent_response.response and agent_response.response.strip():
|
|
654
|
+
logger.debug(
|
|
655
|
+
"Adding agent response as hint",
|
|
656
|
+
extra={
|
|
657
|
+
"response_preview": agent_response.response[:100] + "..."
|
|
658
|
+
if len(agent_response.response) > 100
|
|
659
|
+
else agent_response.response,
|
|
660
|
+
"has_file_operations": len(file_operations) > 0,
|
|
661
|
+
},
|
|
662
|
+
)
|
|
486
663
|
self.ui_message_history.append(
|
|
487
664
|
HintMessage(message=agent_response.response)
|
|
488
665
|
)
|
|
666
|
+
else:
|
|
667
|
+
# Fallback: response is empty or whitespace
|
|
668
|
+
logger.debug(
|
|
669
|
+
"Agent response was empty, using fallback completion message",
|
|
670
|
+
extra={"has_file_operations": len(file_operations) > 0},
|
|
671
|
+
)
|
|
672
|
+
# Show contextual message based on whether files were modified
|
|
673
|
+
if file_operations:
|
|
674
|
+
self.ui_message_history.append(
|
|
675
|
+
HintMessage(
|
|
676
|
+
message="✅ Task completed - files have been modified"
|
|
677
|
+
)
|
|
678
|
+
)
|
|
679
|
+
else:
|
|
680
|
+
self.ui_message_history.append(
|
|
681
|
+
HintMessage(message="✅ Task completed")
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
# Post UI update immediately so user sees the response without delay
|
|
685
|
+
logger.debug(
|
|
686
|
+
"Posting immediate UI update with hint message and file operations"
|
|
687
|
+
)
|
|
688
|
+
self._post_messages_updated(file_operations)
|
|
489
689
|
|
|
490
690
|
# Apply compaction to persistent message history to prevent cascading growth
|
|
491
691
|
all_messages = result.all_messages()
|
|
@@ -517,16 +717,18 @@ class AgentManager(Widget):
|
|
|
517
717
|
self.message_history = all_messages
|
|
518
718
|
|
|
519
719
|
usage = result.usage()
|
|
520
|
-
deps.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
720
|
+
if hasattr(deps, "llm_model") and deps.llm_model is not None:
|
|
721
|
+
deps.usage_manager.add_usage(
|
|
722
|
+
usage, model_name=deps.llm_model.name, provider=deps.llm_model.provider
|
|
723
|
+
)
|
|
724
|
+
else:
|
|
725
|
+
logger.warning(
|
|
726
|
+
"llm_model is None, skipping usage tracking",
|
|
727
|
+
extra={"agent_mode": self._current_agent_type.value},
|
|
728
|
+
)
|
|
527
729
|
|
|
528
|
-
#
|
|
529
|
-
|
|
730
|
+
# UI updates are now posted immediately in each branch (Q&A or non-Q&A)
|
|
731
|
+
# before compaction, so no duplicate posting needed here
|
|
530
732
|
|
|
531
733
|
return result
|
|
532
734
|
|
|
@@ -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
|
|
@@ -12,8 +12,8 @@ POSTHOG_API_KEY = ''
|
|
|
12
12
|
POSTHOG_PROJECT_ID = '191396'
|
|
13
13
|
|
|
14
14
|
# Logfire configuration embedded at build time (only for dev builds)
|
|
15
|
-
LOGFIRE_ENABLED = '
|
|
16
|
-
LOGFIRE_TOKEN = '
|
|
15
|
+
LOGFIRE_ENABLED = ''
|
|
16
|
+
LOGFIRE_TOKEN = ''
|
|
17
17
|
|
|
18
18
|
# Build metadata
|
|
19
19
|
BUILD_TIME_ENV = "production" if SENTRY_DSN else "development"
|