massgen 0.0.3__py3-none-any.whl → 0.1.0__py3-none-any.whl
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 massgen might be problematic. Click here for more details.
- massgen/__init__.py +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1560 -275
- massgen/config_builder.py +2396 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
File Operation Tracker for MassGen
|
|
4
|
+
|
|
5
|
+
This module provides tracking of file operations to enforce read-before-delete policies.
|
|
6
|
+
It ensures agents can only delete files they have read or understood first.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import fnmatch
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Set
|
|
12
|
+
|
|
13
|
+
from ..logger_config import logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FileOperationTracker:
|
|
17
|
+
"""
|
|
18
|
+
Track file operations to enforce read-before-delete policy.
|
|
19
|
+
|
|
20
|
+
This tracker maintains a set of files that have been read by the agent,
|
|
21
|
+
allowing the system to prevent deletion of files that haven't been
|
|
22
|
+
comprehended yet.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Auto-generated file patterns that don't need to be read before deletion
|
|
26
|
+
AUTO_GENERATED_PATTERNS = [
|
|
27
|
+
"__pycache__",
|
|
28
|
+
".pyc",
|
|
29
|
+
".pyo",
|
|
30
|
+
".pytest_cache",
|
|
31
|
+
".mypy_cache",
|
|
32
|
+
".ruff_cache",
|
|
33
|
+
".coverage",
|
|
34
|
+
"*.egg-info",
|
|
35
|
+
".tox",
|
|
36
|
+
".nox",
|
|
37
|
+
"node_modules",
|
|
38
|
+
".next",
|
|
39
|
+
".nuxt",
|
|
40
|
+
"dist",
|
|
41
|
+
"build",
|
|
42
|
+
".DS_Store",
|
|
43
|
+
"Thumbs.db",
|
|
44
|
+
"*.log",
|
|
45
|
+
"*.swp",
|
|
46
|
+
"*.swo",
|
|
47
|
+
"*~",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
def __init__(self, enforce_read_before_delete: bool = True):
|
|
51
|
+
"""
|
|
52
|
+
Initialize the file operation tracker.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
enforce_read_before_delete: Whether to enforce read-before-delete policy
|
|
56
|
+
"""
|
|
57
|
+
self._read_files: Set[Path] = set()
|
|
58
|
+
self._created_files: Set[Path] = set() # Files created by agent (exempt from read requirement)
|
|
59
|
+
self.enforce_read_before_delete = enforce_read_before_delete
|
|
60
|
+
|
|
61
|
+
logger.info(f"[FileOperationTracker] Initialized with enforce_read_before_delete={enforce_read_before_delete}")
|
|
62
|
+
|
|
63
|
+
def mark_as_read(self, file_path: Path) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Mark a file as read/understood by the agent.
|
|
66
|
+
|
|
67
|
+
This is called when the agent uses Read, read_multimodal_files,
|
|
68
|
+
compare_files, or other tools that read file contents.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
file_path: Path to the file that was read
|
|
72
|
+
"""
|
|
73
|
+
resolved_path = file_path.resolve()
|
|
74
|
+
self._read_files.add(resolved_path)
|
|
75
|
+
logger.debug(f"[FileOperationTracker] Marked as read: {resolved_path}")
|
|
76
|
+
|
|
77
|
+
def mark_as_created(self, file_path: Path) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Mark a file as created by the agent during this turn.
|
|
80
|
+
|
|
81
|
+
Files created by the agent are exempt from read-before-delete requirements
|
|
82
|
+
since the agent knows what it created.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
file_path: Path to the file that was created
|
|
86
|
+
"""
|
|
87
|
+
resolved_path = file_path.resolve()
|
|
88
|
+
self._created_files.add(resolved_path)
|
|
89
|
+
logger.debug(f"[FileOperationTracker] Marked as created: {resolved_path}")
|
|
90
|
+
|
|
91
|
+
def was_read(self, file_path: Path) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Check if a file was read by the agent.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
file_path: Path to check
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
True if the file was read or created by the agent
|
|
100
|
+
"""
|
|
101
|
+
resolved_path = file_path.resolve()
|
|
102
|
+
was_read = resolved_path in self._read_files
|
|
103
|
+
was_created = resolved_path in self._created_files
|
|
104
|
+
|
|
105
|
+
logger.debug(f"[FileOperationTracker] Checking read status for {resolved_path}: read={was_read}, created={was_created}")
|
|
106
|
+
|
|
107
|
+
return was_read or was_created
|
|
108
|
+
|
|
109
|
+
def _is_auto_generated(self, file_path: Path) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Check if a file matches auto-generated patterns and is exempt from read-before-delete.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
file_path: Path to check
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if file is auto-generated and can be deleted without reading
|
|
118
|
+
"""
|
|
119
|
+
path_str = str(file_path)
|
|
120
|
+
path_parts = file_path.parts
|
|
121
|
+
|
|
122
|
+
for pattern in self.AUTO_GENERATED_PATTERNS:
|
|
123
|
+
# Check if pattern appears in any part of the path
|
|
124
|
+
if pattern in path_parts:
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
# Check file extensions (patterns starting with .)
|
|
128
|
+
if pattern.startswith(".") and not pattern.startswith(".*"):
|
|
129
|
+
if path_str.endswith(pattern):
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
# Check wildcard patterns (e.g., *.egg-info)
|
|
133
|
+
if "*" in pattern:
|
|
134
|
+
if fnmatch.fnmatch(file_path.name, pattern):
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def can_delete(self, file_path: Path) -> tuple[bool, str | None]:
|
|
140
|
+
"""
|
|
141
|
+
Check if a file can be deleted based on read-before-delete policy.
|
|
142
|
+
|
|
143
|
+
Auto-generated files (like __pycache__, .pyc, etc.) are exempt from
|
|
144
|
+
read-before-delete requirements.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
file_path: Path to the file to check
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Tuple of (can_delete: bool, reason: Optional[str])
|
|
151
|
+
- can_delete: Whether deletion is allowed
|
|
152
|
+
- reason: Explanation if deletion is blocked (None if allowed)
|
|
153
|
+
"""
|
|
154
|
+
if not self.enforce_read_before_delete:
|
|
155
|
+
return (True, None)
|
|
156
|
+
|
|
157
|
+
resolved_path = file_path.resolve()
|
|
158
|
+
|
|
159
|
+
# If file doesn't exist, allow deletion (nothing to delete)
|
|
160
|
+
if not resolved_path.exists():
|
|
161
|
+
return (True, None)
|
|
162
|
+
|
|
163
|
+
# Auto-generated files can be deleted without reading
|
|
164
|
+
if self._is_auto_generated(resolved_path):
|
|
165
|
+
logger.debug(f"[FileOperationTracker] Allowing deletion of auto-generated file: {resolved_path}")
|
|
166
|
+
return (True, None)
|
|
167
|
+
|
|
168
|
+
# Check if file was read or created
|
|
169
|
+
if self.was_read(resolved_path):
|
|
170
|
+
return (True, None)
|
|
171
|
+
|
|
172
|
+
# File hasn't been read - block deletion
|
|
173
|
+
reason = f"Cannot delete '{resolved_path}': File must be read before deletion. " f"Use read (including read_multimodal_files) or diff tools to view the file first."
|
|
174
|
+
logger.info(f"[FileOperationTracker] Blocking deletion: {reason}")
|
|
175
|
+
return (False, reason)
|
|
176
|
+
|
|
177
|
+
def can_delete_directory(self, dir_path: Path) -> tuple[bool, str | None]:
|
|
178
|
+
"""
|
|
179
|
+
Check if a directory can be deleted based on read-before-delete policy.
|
|
180
|
+
|
|
181
|
+
For directories, we check if all files within have been read.
|
|
182
|
+
Auto-generated files are exempt from read-before-delete requirements.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
dir_path: Path to the directory to check
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Tuple of (can_delete: bool, reason: Optional[str])
|
|
189
|
+
- can_delete: Whether deletion is allowed
|
|
190
|
+
- reason: Explanation if deletion is blocked (None if allowed)
|
|
191
|
+
"""
|
|
192
|
+
if not self.enforce_read_before_delete:
|
|
193
|
+
return (True, None)
|
|
194
|
+
|
|
195
|
+
resolved_dir = dir_path.resolve()
|
|
196
|
+
|
|
197
|
+
if not resolved_dir.exists() or not resolved_dir.is_dir():
|
|
198
|
+
# Not a directory or doesn't exist
|
|
199
|
+
return (True, None)
|
|
200
|
+
|
|
201
|
+
# Check all files in directory (excluding auto-generated files)
|
|
202
|
+
unread_files = []
|
|
203
|
+
for file_path in resolved_dir.rglob("*"):
|
|
204
|
+
if file_path.is_file():
|
|
205
|
+
# Skip auto-generated files
|
|
206
|
+
if self._is_auto_generated(file_path):
|
|
207
|
+
continue
|
|
208
|
+
# Skip files that were read
|
|
209
|
+
if not self.was_read(file_path):
|
|
210
|
+
unread_files.append(str(file_path.relative_to(resolved_dir)))
|
|
211
|
+
|
|
212
|
+
if unread_files:
|
|
213
|
+
# Limit to first 3 unread files for readable error message
|
|
214
|
+
example_files = unread_files[:3]
|
|
215
|
+
suffix = f" (and {len(unread_files) - 3} more)" if len(unread_files) > 3 else ""
|
|
216
|
+
reason = f"Cannot delete directory '{resolved_dir}': Contains {len(unread_files)} unread file(s). " f"Examples: {', '.join(example_files)}{suffix}. " f"Please read files before deletion."
|
|
217
|
+
logger.info(f"[FileOperationTracker] Blocking directory deletion: {reason}")
|
|
218
|
+
return (False, reason)
|
|
219
|
+
|
|
220
|
+
return (True, None)
|
|
221
|
+
|
|
222
|
+
def clear(self) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Clear all tracked operations.
|
|
225
|
+
|
|
226
|
+
This should be called at the start of each agent's turn to reset
|
|
227
|
+
the tracker state.
|
|
228
|
+
"""
|
|
229
|
+
read_count = len(self._read_files)
|
|
230
|
+
created_count = len(self._created_files)
|
|
231
|
+
|
|
232
|
+
self._read_files.clear()
|
|
233
|
+
self._created_files.clear()
|
|
234
|
+
|
|
235
|
+
logger.info(f"[FileOperationTracker] Cleared tracker (had {read_count} read files, {created_count} created files)")
|
|
236
|
+
|
|
237
|
+
def get_stats(self) -> dict[str, int]:
|
|
238
|
+
"""
|
|
239
|
+
Get statistics about tracked operations.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Dictionary with tracking statistics
|
|
243
|
+
"""
|
|
244
|
+
return {
|
|
245
|
+
"read_files": len(self._read_files),
|
|
246
|
+
"created_files": len(self._created_files),
|
|
247
|
+
"total_tracked": len(self._read_files) + len(self._created_files),
|
|
248
|
+
}
|