jarvis-ai-assistant 0.3.30__tar.gz → 0.3.32__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.
- {jarvis_ai_assistant-0.3.30/src/jarvis_ai_assistant.egg-info → jarvis_ai_assistant-0.3.32}/PKG-INFO +1 -1
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/pyproject.toml +1 -1
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/setup.py +1 -1
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/__init__.py +1 -1
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/__init__.py +22 -2
- jarvis_ai_assistant-0.3.32/src/jarvis/jarvis_agent/edit_file_handler.py +561 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/jarvis.py +83 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_agent/code_agent.py +88 -6
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_data/config_schema.json +38 -4
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/openai.py +26 -1
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/edit_file.py +39 -10
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/read_code.py +12 -11
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/registry.py +16 -15
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/config.py +17 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/embedding.py +3 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/git_utils.py +14 -2
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/input.py +1 -1
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/utils.py +63 -9
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32/src/jarvis_ai_assistant.egg-info}/PKG-INFO +1 -1
- jarvis_ai_assistant-0.3.30/src/jarvis/jarvis_agent/edit_file_handler.py +0 -296
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/MANIFEST.in +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/README.md +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/setup.cfg +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/agent_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/builtin_input_handler.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/config.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/config_editor.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/event_bus.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/events.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/file_methodology_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/memory_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/methodology_share_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/output_handler.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/prompt_builder.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/prompt_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/prompts.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/protocols.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/run_loop.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/session_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/share_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/shell_input_handler.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/task_analyzer.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/task_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/tool_executor.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/tool_share_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/user_interaction.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/utils.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_agent/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_agent/lint.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/c_cpp.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/csharp.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/data_format.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/devops.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/docs.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/go.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/infrastructure.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/java.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/javascript.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/kotlin.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/loader.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/php.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/python.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/ruby.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/rust.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/shell.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/sql.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/swift.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/checklists/web.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_code_analysis/code_review.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_data/tiktoken/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_git_squash/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_git_squash/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_git_utils/git_commiter.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_mcp/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_mcp/sse_mcp_client.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_mcp/stdio_mcp_client.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_mcp/streamable_mcp_client.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_memory_organizer/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_memory_organizer/memory_organizer.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_methodology/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_multi_agent/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_multi_agent/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/ai8.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/base.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/human.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/kimi.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/registry.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/tongyi.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform/yuanbao.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform_manager/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform_manager/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_platform_manager/service.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/cache.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/cli.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/embedding_manager.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/llm_interface.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/query_rewriter.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/rag_pipeline.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/reranker.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_rag/retriever.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_smart_shell/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_smart_shell/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_stats/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_stats/cli.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_stats/stats.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_stats/storage.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_stats/visualizer.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/ask_user.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/base.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/clear_memory.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/cli/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/cli/main.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/execute_script.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/file_analyzer.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/generate_new_tool.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/methodology.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/read_webpage.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/retrieve_memory.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/rewrite_file.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/save_memory.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/search_web.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/sub_agent.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/sub_code_agent.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_tools/virtual_tty.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/__init__.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/builtin_replace_map.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/clipboard.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/file_processors.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/fzf.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/globals.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/http.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/methodology.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/output.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_utils/tag.py +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis_ai_assistant.egg-info/SOURCES.txt +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis_ai_assistant.egg-info/dependency_links.txt +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis_ai_assistant.egg-info/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis_ai_assistant.egg-info/requires.txt +0 -0
- {jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis_ai_assistant.egg-info/top_level.txt +0 -0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "jarvis-ai-assistant"
|
7
|
-
version = "0.3.
|
7
|
+
version = "0.3.32"
|
8
8
|
description = "Jarvis: An AI assistant that uses tools to interact with the system"
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [{ name = "skyfire", email = "skyfireitdiy@hotmail.com" }]
|
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages # type: ignore
|
|
3
3
|
|
4
4
|
setup(
|
5
5
|
name="jarvis-ai-assistant",
|
6
|
-
version="0.3.
|
6
|
+
version="0.3.32",
|
7
7
|
author="skyfire",
|
8
8
|
author_email="skyfireitdiy@hotmail.com",
|
9
9
|
description="An AI assistant that uses various tools to interact with the system",
|
{jarvis_ai_assistant-0.3.30 → jarvis_ai_assistant-0.3.32}/src/jarvis/jarvis_agent/__init__.py
RENAMED
@@ -729,7 +729,14 @@ class Agent:
|
|
729
729
|
pass
|
730
730
|
|
731
731
|
response = self.model.chat_until_success(message) # type: ignore
|
732
|
-
|
732
|
+
# 防御: 模型可能返回空响应(None或空字符串),统一为空字符串并告警
|
733
|
+
if not response:
|
734
|
+
try:
|
735
|
+
PrettyOutput.print("模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
736
|
+
except Exception:
|
737
|
+
pass
|
738
|
+
response = ""
|
739
|
+
|
733
740
|
# 事件:模型调用后
|
734
741
|
try:
|
735
742
|
self.event_bus.emit(
|
@@ -761,7 +768,13 @@ class Agent:
|
|
761
768
|
summary = self.model.chat_until_success(
|
762
769
|
self.session.prompt + "\n" + SUMMARY_REQUEST_PROMPT
|
763
770
|
) # type: ignore
|
764
|
-
|
771
|
+
# 防御: 可能返回空响应(None或空字符串),统一为空字符串并告警
|
772
|
+
if not summary:
|
773
|
+
try:
|
774
|
+
PrettyOutput.print("总结模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
775
|
+
except Exception:
|
776
|
+
pass
|
777
|
+
summary = ""
|
765
778
|
return summary
|
766
779
|
except Exception:
|
767
780
|
PrettyOutput.print("总结对话历史失败", OutputType.ERROR)
|
@@ -898,6 +911,13 @@ class Agent:
|
|
898
911
|
if not self.model:
|
899
912
|
raise RuntimeError("Model not initialized")
|
900
913
|
ret = self.model.chat_until_success(self.session.prompt) # type: ignore
|
914
|
+
# 防御: 总结阶段模型可能返回空响应(None或空字符串),统一为空字符串并告警
|
915
|
+
if not ret:
|
916
|
+
try:
|
917
|
+
PrettyOutput.print("总结阶段模型返回空响应,已使用空字符串回退。", OutputType.WARNING)
|
918
|
+
except Exception:
|
919
|
+
pass
|
920
|
+
ret = ""
|
901
921
|
result = ret
|
902
922
|
|
903
923
|
# 广播完成总结事件
|
@@ -0,0 +1,561 @@
|
|
1
|
+
import os
|
2
|
+
import re
|
3
|
+
from typing import Any, Dict, List, Tuple
|
4
|
+
|
5
|
+
from jarvis.jarvis_agent.output_handler import OutputHandler
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
7
|
+
from jarvis.jarvis_utils.tag import ct, ot
|
8
|
+
|
9
|
+
|
10
|
+
class EditFileHandler(OutputHandler):
|
11
|
+
def __init__(self):
|
12
|
+
self.patch_pattern = re.compile(
|
13
|
+
ot("PATCH file=(?:'([^']+)'|\"([^\"]+)\"|([^>]+))") + r"\s*"
|
14
|
+
r"(?:"
|
15
|
+
+ ot("DIFF")
|
16
|
+
+ r"\s*(?:"
|
17
|
+
# 可选的RANGE标签,限制替换行号范围
|
18
|
+
+ r"(?:" + ot("RANGE") + r"(.*?)" + ct("RANGE") + r"\s*)?"
|
19
|
+
+ r"(?:"
|
20
|
+
# 单点替换(SEARCH/REPLACE)
|
21
|
+
+ ot("SEARCH")
|
22
|
+
+ r"(.*?)"
|
23
|
+
+ ct("SEARCH")
|
24
|
+
+ r"\s*"
|
25
|
+
+ ot("REPLACE")
|
26
|
+
+ r"(.*?)"
|
27
|
+
+ ct("REPLACE")
|
28
|
+
+ r"|"
|
29
|
+
# 区间替换(SEARCH_START/SEARCH_END/REPLACE)
|
30
|
+
+ ot("SEARCH_START")
|
31
|
+
+ r"(.*?)"
|
32
|
+
+ ct("SEARCH_START")
|
33
|
+
+ r"\s*"
|
34
|
+
+ ot("SEARCH_END")
|
35
|
+
+ r"(.*?)"
|
36
|
+
+ ct("SEARCH_END")
|
37
|
+
+ r"\s*"
|
38
|
+
+ ot("REPLACE")
|
39
|
+
+ r"(.*?)"
|
40
|
+
+ ct("REPLACE")
|
41
|
+
+ r")"
|
42
|
+
+ r")\s*"
|
43
|
+
+ ct("DIFF")
|
44
|
+
+ r"\s*)+"
|
45
|
+
+ r"^" + ct("PATCH"),
|
46
|
+
re.DOTALL | re.MULTILINE,
|
47
|
+
)
|
48
|
+
self.diff_pattern = re.compile(
|
49
|
+
ot("DIFF")
|
50
|
+
+ r"\s*(?:" + ot("RANGE") + r"(.*?)" + ct("RANGE") + r"\s*)?"
|
51
|
+
+ ot("SEARCH")
|
52
|
+
+ r"(.*?)"
|
53
|
+
+ ct("SEARCH")
|
54
|
+
+ r"\s*"
|
55
|
+
+ ot("REPLACE")
|
56
|
+
+ r"(.*?)"
|
57
|
+
+ ct("REPLACE")
|
58
|
+
+ r"\s*"
|
59
|
+
+ ct("DIFF"),
|
60
|
+
re.DOTALL,
|
61
|
+
)
|
62
|
+
self.diff_range_pattern = re.compile(
|
63
|
+
ot("DIFF")
|
64
|
+
+ r"\s*(?:" + ot("RANGE") + r"(.*?)" + ct("RANGE") + r"\s*)?"
|
65
|
+
+ ot("SEARCH_START")
|
66
|
+
+ r"(.*?)"
|
67
|
+
+ ct("SEARCH_START")
|
68
|
+
+ r"\s*"
|
69
|
+
+ ot("SEARCH_END")
|
70
|
+
+ r"(.*?)"
|
71
|
+
+ ct("SEARCH_END")
|
72
|
+
+ r"\s*"
|
73
|
+
+ ot("REPLACE")
|
74
|
+
+ r"(.*?)"
|
75
|
+
+ ct("REPLACE")
|
76
|
+
+ r"\s*"
|
77
|
+
+ ct("DIFF"),
|
78
|
+
re.DOTALL,
|
79
|
+
)
|
80
|
+
|
81
|
+
def handle(self, response: str, agent: Any) -> Tuple[bool, str]:
|
82
|
+
"""处理文件编辑响应
|
83
|
+
|
84
|
+
Args:
|
85
|
+
response: 包含文件编辑指令的响应字符串
|
86
|
+
agent: 执行处理的agent实例
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
Tuple[bool, str]: 返回处理结果元组,第一个元素表示是否处理成功,第二个元素为处理结果汇总字符串
|
90
|
+
"""
|
91
|
+
patches = self._parse_patches(response)
|
92
|
+
if not patches:
|
93
|
+
return False, "未找到有效的文件编辑指令"
|
94
|
+
|
95
|
+
# 记录 edit_file 工具调用统计
|
96
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
97
|
+
|
98
|
+
StatsManager.increment("edit_file", group="tool")
|
99
|
+
|
100
|
+
results = []
|
101
|
+
|
102
|
+
for file_path, diffs in patches.items():
|
103
|
+
file_path = os.path.abspath(file_path)
|
104
|
+
file_patches = diffs
|
105
|
+
|
106
|
+
success, result = self._fast_edit(file_path, file_patches)
|
107
|
+
|
108
|
+
if success:
|
109
|
+
results.append(f"✅ 文件 {file_path} 修改成功")
|
110
|
+
else:
|
111
|
+
results.append(f"❌ 文件 {file_path} 修改失败: {result}")
|
112
|
+
|
113
|
+
summary = "\n".join(results)
|
114
|
+
return False, summary
|
115
|
+
|
116
|
+
def can_handle(self, response: str) -> bool:
|
117
|
+
"""判断是否能处理给定的响应
|
118
|
+
|
119
|
+
Args:
|
120
|
+
response: 需要判断的响应字符串
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
bool: 返回是否能处理该响应
|
124
|
+
"""
|
125
|
+
return bool(self.patch_pattern.search(response))
|
126
|
+
|
127
|
+
def prompt(self) -> str:
|
128
|
+
"""获取处理器的提示信息
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
str: 返回处理器的提示字符串
|
132
|
+
"""
|
133
|
+
from jarvis.jarvis_utils.config import get_patch_format
|
134
|
+
|
135
|
+
patch_format = get_patch_format()
|
136
|
+
|
137
|
+
search_prompt = f"""{ot("DIFF")}
|
138
|
+
{ot("SEARCH")}原始代码{ct("SEARCH")}
|
139
|
+
{ot("REPLACE")}新代码{ct("REPLACE")}
|
140
|
+
{ct("DIFF")}"""
|
141
|
+
|
142
|
+
search_range_prompt = f"""{ot("DIFF")}
|
143
|
+
{ot("RANGE")}起止行号(如: 10-50),可选{ct("RANGE")}
|
144
|
+
{ot("SEARCH_START")}起始标记{ct("SEARCH_START")}
|
145
|
+
{ot("SEARCH_END")}结束标记{ct("SEARCH_END")}
|
146
|
+
{ot("REPLACE")}替换内容{ct("REPLACE")}
|
147
|
+
{ct("DIFF")}"""
|
148
|
+
|
149
|
+
if patch_format == "search":
|
150
|
+
formats = search_prompt
|
151
|
+
supported_formats = "仅支持单点替换(SEARCH/REPLACE)"
|
152
|
+
elif patch_format == "search_range":
|
153
|
+
formats = search_range_prompt
|
154
|
+
supported_formats = "仅支持区间替换(SEARCH_START/SEARCH_END/REPLACE),可选RANGE限定行号范围"
|
155
|
+
else: # all
|
156
|
+
formats = f"{search_prompt}\n或\n{search_range_prompt}"
|
157
|
+
supported_formats = "支持两种DIFF块:单点替换(SEARCH/REPLACE)与区间替换(SEARCH_START/SEARCH_END/REPLACE)"
|
158
|
+
|
159
|
+
return f"""文件编辑指令格式:
|
160
|
+
{ot("PATCH file=文件路径")}
|
161
|
+
{formats}
|
162
|
+
{ct("PATCH")}
|
163
|
+
|
164
|
+
注意:
|
165
|
+
- {ot("PATCH")} 和 {ct("PATCH")} 必须出现在行首,否则不生效(会被忽略)
|
166
|
+
- {supported_formats}
|
167
|
+
- {ot("RANGE")}start-end{ct("RANGE")} 仅用于区间替换模式(SEARCH_START/SEARCH_END),表示只在指定行号范围内进行匹配与替换(1-based,闭区间);省略则在整个文件范围内处理
|
168
|
+
- 单点替换要求 SEARCH 在有效范围内唯一匹配(仅替换第一个匹配)
|
169
|
+
- 区间替换会从包含 {ot("SEARCH_START")} 的行首开始,到包含 {ot("SEARCH_END")} 的行尾结束,替换整个区域
|
170
|
+
否则编辑将失败。"""
|
171
|
+
|
172
|
+
def name(self) -> str:
|
173
|
+
"""获取处理器的名称
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
str: 返回处理器的名称字符串
|
177
|
+
"""
|
178
|
+
return "PATCH"
|
179
|
+
|
180
|
+
def _parse_patches(self, response: str) -> Dict[str, List[Dict[str, str]]]:
|
181
|
+
"""解析响应中的补丁信息
|
182
|
+
|
183
|
+
该方法使用正则表达式从响应文本中提取文件编辑指令(PATCH块),
|
184
|
+
每个PATCH块可以包含多个DIFF块,每个DIFF块包含一组搜索和替换内容。
|
185
|
+
解析后会返回一个字典,键是文件路径,值是该文件对应的补丁列表。
|
186
|
+
如果同一个文件路径出现多次,会将所有DIFF块合并到一起。
|
187
|
+
|
188
|
+
Args:
|
189
|
+
response: 包含补丁信息的响应字符串,格式应符合PATCH指令规范
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
Dict[str, List[Dict[str, str]]]:
|
193
|
+
返回解析后的补丁信息字典,结构为:
|
194
|
+
{
|
195
|
+
"文件路径1": [
|
196
|
+
{"SEARCH": "搜索文本1", "REPLACE": "替换文本1"},
|
197
|
+
{"SEARCH": "搜索文本2", "REPLACE": "替换文本2"}
|
198
|
+
],
|
199
|
+
"文件路径2": [...]
|
200
|
+
}
|
201
|
+
"""
|
202
|
+
patches: Dict[str, List[Dict[str, str]]] = {}
|
203
|
+
|
204
|
+
for match in self.patch_pattern.finditer(response):
|
205
|
+
# Get the file path from the appropriate capture group
|
206
|
+
file_path = match.group(1) or match.group(2) or match.group(3)
|
207
|
+
diffs: List[Dict[str, str]] = []
|
208
|
+
|
209
|
+
# 逐块解析,保持 DIFF 顺序
|
210
|
+
diff_block_pattern = re.compile(ot("DIFF") + r"(.*?)" + ct("DIFF"), re.DOTALL)
|
211
|
+
for block_match in diff_block_pattern.finditer(match.group(0)):
|
212
|
+
block_text = block_match.group(1)
|
213
|
+
|
214
|
+
# 提取可选的行号范围
|
215
|
+
range_scope = None
|
216
|
+
range_scope_match = re.match(
|
217
|
+
r"^\s*" + ot("RANGE") + r"(.*?)" + ct("RANGE") + r"\s*",
|
218
|
+
block_text,
|
219
|
+
re.DOTALL,
|
220
|
+
)
|
221
|
+
if range_scope_match:
|
222
|
+
range_scope = range_scope_match.group(1).strip()
|
223
|
+
# 仅移除块首部的RANGE标签,避免误删内容中的同名标记
|
224
|
+
block_text = block_text[range_scope_match.end():]
|
225
|
+
# 统一按 all 解析:无视配置,始终尝试区间替换
|
226
|
+
range_match = re.search(
|
227
|
+
ot("SEARCH_START")
|
228
|
+
+ r"(.*?)"
|
229
|
+
+ ct("SEARCH_START")
|
230
|
+
+ r"\s*"
|
231
|
+
+ ot("SEARCH_END")
|
232
|
+
+ r"(.*?)"
|
233
|
+
+ ct("SEARCH_END")
|
234
|
+
+ r"\s*"
|
235
|
+
+ ot("REPLACE")
|
236
|
+
+ r"(.*?)"
|
237
|
+
+ ct("REPLACE"),
|
238
|
+
block_text,
|
239
|
+
re.DOTALL,
|
240
|
+
)
|
241
|
+
if range_match:
|
242
|
+
diff_item: Dict[str, str] = {
|
243
|
+
"SEARCH_START": range_match.group(1), # 原始SEARCH_START内容
|
244
|
+
"SEARCH_END": range_match.group(2), # 原始SEARCH_END内容
|
245
|
+
"REPLACE": range_match.group(3), # 原始REPLACE内容
|
246
|
+
}
|
247
|
+
if range_scope:
|
248
|
+
diff_item["RANGE"] = range_scope
|
249
|
+
diffs.append(diff_item)
|
250
|
+
continue
|
251
|
+
|
252
|
+
# 解析单点替换(统一按 all 解析:无视配置,始终尝试单点替换)
|
253
|
+
single_match = re.search(
|
254
|
+
ot("SEARCH")
|
255
|
+
+ r"(.*?)"
|
256
|
+
+ ct("SEARCH")
|
257
|
+
+ r"\s*"
|
258
|
+
+ ot("REPLACE")
|
259
|
+
+ r"(.*?)"
|
260
|
+
+ ct("REPLACE"),
|
261
|
+
block_text,
|
262
|
+
re.DOTALL,
|
263
|
+
)
|
264
|
+
if single_match:
|
265
|
+
diff_item = {
|
266
|
+
"SEARCH": single_match.group(1), # 原始SEARCH内容
|
267
|
+
"REPLACE": single_match.group(2), # 原始REPLACE内容
|
268
|
+
}
|
269
|
+
# SEARCH 模式不支持 RANGE,直接忽略
|
270
|
+
diffs.append(diff_item)
|
271
|
+
|
272
|
+
if diffs:
|
273
|
+
if file_path in patches:
|
274
|
+
patches[file_path].extend(diffs)
|
275
|
+
else:
|
276
|
+
patches[file_path] = diffs
|
277
|
+
return patches
|
278
|
+
|
279
|
+
@staticmethod
|
280
|
+
def _fast_edit(file_path: str, patches: List[Dict[str, str]]) -> Tuple[bool, str]:
|
281
|
+
"""快速应用补丁到文件
|
282
|
+
|
283
|
+
该方法直接尝试将补丁应用到目标文件,适用于简单、明确的修改场景。
|
284
|
+
特点:
|
285
|
+
1. 直接进行字符串替换,效率高
|
286
|
+
2. 会自动处理缩进问题,尝试匹配不同缩进级别的代码
|
287
|
+
3. 确保搜索文本在文件中唯一匹配
|
288
|
+
4. 如果部分补丁失败,会继续应用剩余补丁,并报告失败信息
|
289
|
+
|
290
|
+
Args:
|
291
|
+
file_path: 要修改的文件路径,支持绝对路径和相对路径
|
292
|
+
patches: 补丁列表,每个补丁包含search(搜索文本)和replace(替换文本)
|
293
|
+
|
294
|
+
Returns:
|
295
|
+
Tuple[bool, str]:
|
296
|
+
返回处理结果元组,第一个元素表示是否所有补丁都成功应用,
|
297
|
+
第二个元素为结果信息,全部成功时为修改后的文件内容,部分或全部失败时为错误信息
|
298
|
+
"""
|
299
|
+
try:
|
300
|
+
# 确保目录存在
|
301
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
302
|
+
|
303
|
+
# 读取原始文件内容
|
304
|
+
file_content = ""
|
305
|
+
if os.path.exists(file_path):
|
306
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
307
|
+
file_content = f.read()
|
308
|
+
|
309
|
+
# 应用所有补丁
|
310
|
+
modified_content = file_content
|
311
|
+
patch_count = 0
|
312
|
+
failed_patches: List[Dict[str, Any]] = []
|
313
|
+
successful_patches = 0
|
314
|
+
|
315
|
+
# 当存在RANGE时,确保按行号从后往前应用补丁,避免前面补丁影响后续RANGE的行号
|
316
|
+
ordered_patches: List[Dict[str, str]] = []
|
317
|
+
range_items: List[Tuple[int, int, int, Dict[str, str]]] = []
|
318
|
+
non_range_items: List[Tuple[int, Dict[str, str]]] = []
|
319
|
+
for idx, p in enumerate(patches):
|
320
|
+
r = p.get("RANGE")
|
321
|
+
if r and str(r).strip():
|
322
|
+
m = re.match(r"\s*(\d+)\s*-\s*(\d+)\s*$", str(r))
|
323
|
+
if m:
|
324
|
+
start_line = int(m.group(1))
|
325
|
+
end_line = int(m.group(2))
|
326
|
+
range_items.append((start_line, end_line, idx, p))
|
327
|
+
else:
|
328
|
+
# RANGE格式无效的补丁保持原有顺序
|
329
|
+
non_range_items.append((idx, p))
|
330
|
+
else:
|
331
|
+
# 无RANGE的补丁保持原有顺序
|
332
|
+
non_range_items.append((idx, p))
|
333
|
+
# 先应用RANGE补丁:按start_line、end_line、原始索引逆序
|
334
|
+
range_items.sort(key=lambda x: (x[0], x[1], x[2]), reverse=True)
|
335
|
+
ordered_patches = [item[3] for item in range_items] + [item[1] for item in non_range_items]
|
336
|
+
|
337
|
+
patch_count = len(ordered_patches)
|
338
|
+
for patch in ordered_patches:
|
339
|
+
found = False
|
340
|
+
|
341
|
+
# 处理可选的RANGE范围:格式 "start-end"(1-based, 闭区间)
|
342
|
+
scoped = False
|
343
|
+
prefix = suffix = ""
|
344
|
+
base_content = modified_content
|
345
|
+
if "RANGE" in patch and str(patch["RANGE"]).strip():
|
346
|
+
m = re.match(r"\s*(\d+)\s*-\s*(\d+)\s*$", str(patch["RANGE"]))
|
347
|
+
if not m:
|
348
|
+
error_msg = "RANGE格式无效,应为 'start-end' 的行号范围(1-based, 闭区间)"
|
349
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
350
|
+
# 不进行本补丁其它处理
|
351
|
+
continue
|
352
|
+
start_line = int(m.group(1))
|
353
|
+
end_line = int(m.group(2))
|
354
|
+
|
355
|
+
# 拆分为三段
|
356
|
+
lines = modified_content.splitlines(keepends=True)
|
357
|
+
total_lines = len(lines)
|
358
|
+
if (
|
359
|
+
start_line < 1
|
360
|
+
or end_line < 1
|
361
|
+
or start_line > end_line
|
362
|
+
or start_line > total_lines
|
363
|
+
):
|
364
|
+
error_msg = f"RANGE行号无效(文件共有{total_lines}行)"
|
365
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
366
|
+
continue
|
367
|
+
# 截断end_line不超过总行数
|
368
|
+
end_line = min(end_line, total_lines)
|
369
|
+
|
370
|
+
prefix = "".join(lines[: start_line - 1])
|
371
|
+
base_content = "".join(lines[start_line - 1 : end_line])
|
372
|
+
suffix = "".join(lines[end_line:])
|
373
|
+
scoped = True
|
374
|
+
|
375
|
+
# 单点替换
|
376
|
+
if "SEARCH" in patch:
|
377
|
+
search_text = patch["SEARCH"]
|
378
|
+
replace_text = patch["REPLACE"]
|
379
|
+
|
380
|
+
# 精确匹配搜索文本(保留原始换行和空格)
|
381
|
+
exact_search = search_text
|
382
|
+
|
383
|
+
def _count_occurrences(haystack: str, needle: str) -> int:
|
384
|
+
if not needle:
|
385
|
+
return 0
|
386
|
+
return haystack.count(needle)
|
387
|
+
|
388
|
+
# 1) 精确匹配,要求唯一
|
389
|
+
cnt = _count_occurrences(base_content, exact_search)
|
390
|
+
if cnt == 1:
|
391
|
+
base_content = base_content.replace(exact_search, replace_text, 1)
|
392
|
+
found = True
|
393
|
+
elif cnt > 1:
|
394
|
+
error_msg = "SEARCH 在指定范围内出现多次,要求唯一匹配"
|
395
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
396
|
+
# 不继续尝试其它变体
|
397
|
+
continue
|
398
|
+
else:
|
399
|
+
# 2) 若首尾均为换行,尝试去掉首尾换行后匹配,要求唯一
|
400
|
+
if (
|
401
|
+
search_text.startswith("\n")
|
402
|
+
and search_text.endswith("\n")
|
403
|
+
and replace_text.startswith("\n")
|
404
|
+
and replace_text.endswith("\n")
|
405
|
+
):
|
406
|
+
stripped_search = search_text[1:-1]
|
407
|
+
stripped_replace = replace_text[1:-1]
|
408
|
+
cnt2 = _count_occurrences(base_content, stripped_search)
|
409
|
+
if cnt2 == 1:
|
410
|
+
base_content = base_content.replace(
|
411
|
+
stripped_search, stripped_replace, 1
|
412
|
+
)
|
413
|
+
found = True
|
414
|
+
elif cnt2 > 1:
|
415
|
+
error_msg = "SEARCH 在指定范围内出现多次(去掉首尾换行后),要求唯一匹配"
|
416
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
417
|
+
continue
|
418
|
+
|
419
|
+
# 3) 尝试缩进适配(1..16个空格),要求唯一
|
420
|
+
if not found:
|
421
|
+
current_search = search_text
|
422
|
+
current_replace = replace_text
|
423
|
+
if (
|
424
|
+
current_search.startswith("\n")
|
425
|
+
and current_search.endswith("\n")
|
426
|
+
and current_replace.startswith("\n")
|
427
|
+
and current_replace.endswith("\n")
|
428
|
+
):
|
429
|
+
current_search = current_search[1:-1]
|
430
|
+
current_replace = current_replace[1:-1]
|
431
|
+
|
432
|
+
for space_count in range(1, 17):
|
433
|
+
indented_search = "\n".join(
|
434
|
+
" " * space_count + line if line.strip() else line
|
435
|
+
for line in current_search.split("\n")
|
436
|
+
)
|
437
|
+
indented_replace = "\n".join(
|
438
|
+
" " * space_count + line if line.strip() else line
|
439
|
+
for line in current_replace.split("\n")
|
440
|
+
)
|
441
|
+
cnt3 = _count_occurrences(base_content, indented_search)
|
442
|
+
if cnt3 == 1:
|
443
|
+
base_content = base_content.replace(
|
444
|
+
indented_search, indented_replace, 1
|
445
|
+
)
|
446
|
+
found = True
|
447
|
+
break
|
448
|
+
elif cnt3 > 1:
|
449
|
+
error_msg = "SEARCH 在指定范围内出现多次(缩进适配后),要求唯一匹配"
|
450
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
451
|
+
# 多匹配直接失败,不再继续尝试其它缩进
|
452
|
+
found = False
|
453
|
+
break
|
454
|
+
|
455
|
+
if not found:
|
456
|
+
# 未找到任何可用的唯一匹配
|
457
|
+
failed_patches.append({"patch": patch, "error": "未找到唯一匹配的SEARCH"})
|
458
|
+
|
459
|
+
# 区间替换
|
460
|
+
elif "SEARCH_START" in patch and "SEARCH_END" in patch:
|
461
|
+
search_start = patch["SEARCH_START"]
|
462
|
+
search_end = patch["SEARCH_END"]
|
463
|
+
replace_text = patch["REPLACE"]
|
464
|
+
|
465
|
+
# 范围替换(包含边界),命中第一个起始标记及其后的第一个结束标记
|
466
|
+
start_idx = base_content.find(search_start)
|
467
|
+
if start_idx == -1:
|
468
|
+
error_msg = "未找到SEARCH_START"
|
469
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
470
|
+
else:
|
471
|
+
# 从 search_start 之后开始查找 search_end
|
472
|
+
end_idx = base_content.find(search_end, start_idx + len(search_start))
|
473
|
+
if end_idx == -1:
|
474
|
+
error_msg = "在SEARCH_START之后未找到SEARCH_END"
|
475
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
476
|
+
else:
|
477
|
+
# 将替换范围扩展到整行
|
478
|
+
# 找到 start_idx 所在行的行首
|
479
|
+
line_start_idx = base_content.rfind("\n", 0, start_idx) + 1
|
480
|
+
|
481
|
+
# 找到 end_idx 所在行的行尾
|
482
|
+
match_end_pos = end_idx + len(search_end)
|
483
|
+
line_end_idx = base_content.find("\n", match_end_pos)
|
484
|
+
|
485
|
+
if line_end_idx == -1:
|
486
|
+
# 如果没有找到换行符,说明是最后一行
|
487
|
+
end_of_range = len(base_content)
|
488
|
+
else:
|
489
|
+
# 包含换行符
|
490
|
+
end_of_range = line_end_idx + 1
|
491
|
+
|
492
|
+
final_replace_text = replace_text
|
493
|
+
original_slice = base_content[line_start_idx:end_of_range]
|
494
|
+
|
495
|
+
# 如果原始片段以换行符结尾,且替换内容不为空且不以换行符结尾,
|
496
|
+
# 则为替换内容添加换行符以保持格式
|
497
|
+
if (
|
498
|
+
final_replace_text
|
499
|
+
and original_slice.endswith("\n")
|
500
|
+
and not final_replace_text.endswith("\n")
|
501
|
+
):
|
502
|
+
final_replace_text += "\n"
|
503
|
+
|
504
|
+
base_content = (
|
505
|
+
base_content[:line_start_idx]
|
506
|
+
+ final_replace_text
|
507
|
+
+ base_content[end_of_range:]
|
508
|
+
)
|
509
|
+
found = True
|
510
|
+
|
511
|
+
else:
|
512
|
+
error_msg = "不支持的补丁格式"
|
513
|
+
failed_patches.append({"patch": patch, "error": error_msg})
|
514
|
+
|
515
|
+
# 若使用了RANGE,则将局部修改写回整体内容
|
516
|
+
if found:
|
517
|
+
if scoped:
|
518
|
+
modified_content = prefix + base_content + suffix
|
519
|
+
else:
|
520
|
+
modified_content = base_content
|
521
|
+
successful_patches += 1
|
522
|
+
|
523
|
+
# 写入修改后的内容
|
524
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
525
|
+
f.write(modified_content)
|
526
|
+
|
527
|
+
if failed_patches:
|
528
|
+
error_details = []
|
529
|
+
for p in failed_patches:
|
530
|
+
patch = p["patch"]
|
531
|
+
if "SEARCH" in patch:
|
532
|
+
patch_desc = patch["SEARCH"]
|
533
|
+
else:
|
534
|
+
patch_desc = (
|
535
|
+
"SEARCH_START:\n"
|
536
|
+
+ (patch.get("SEARCH_START", ""))
|
537
|
+
+ "\nSEARCH_END:\n"
|
538
|
+
+ (patch.get("SEARCH_END", ""))
|
539
|
+
)
|
540
|
+
error_details.append(f" - 失败的补丁: \n{patch_desc}\n 错误: {p['error']}")
|
541
|
+
if successful_patches == 0:
|
542
|
+
summary = (
|
543
|
+
f"文件 {file_path} 修改失败(全部失败)。\n"
|
544
|
+
f"失败: {len(failed_patches)}/{patch_count}.\n"
|
545
|
+
f"失败详情:\n" + "\n".join(error_details)
|
546
|
+
)
|
547
|
+
else:
|
548
|
+
summary = (
|
549
|
+
f"文件 {file_path} 修改部分成功。\n"
|
550
|
+
f"成功: {successful_patches}/{patch_count}, "
|
551
|
+
f"失败: {len(failed_patches)}/{patch_count}.\n"
|
552
|
+
f"失败详情:\n" + "\n".join(error_details)
|
553
|
+
)
|
554
|
+
PrettyOutput.print(summary, OutputType.ERROR)
|
555
|
+
return False, summary
|
556
|
+
|
557
|
+
return True, modified_content
|
558
|
+
|
559
|
+
except Exception as e:
|
560
|
+
PrettyOutput.print(f"文件修改失败: {str(e)}", OutputType.ERROR)
|
561
|
+
return False, f"文件修改失败: {str(e)}"
|