jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/prompts.py
CHANGED
|
@@ -43,13 +43,28 @@ SUMMARY_REQUEST_PROMPT = """<summary_request>
|
|
|
43
43
|
|
|
44
44
|
TASK_ANALYSIS_PROMPT = f"""<task_analysis>
|
|
45
45
|
<request>
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
当前任务已结束,请按以下步骤分析该任务:
|
|
47
|
+
|
|
48
|
+
第一步:记忆值得保存的信息
|
|
49
|
+
1. 识别任务中的关键信息和知识点
|
|
50
|
+
2. 评估是否有值得保存的项目长期记忆或全局长期记忆
|
|
51
|
+
3. 使用 save_memory 工具保存有价值的信息:
|
|
52
|
+
- project_long_term: 保存与当前项目相关的长期信息(如项目配置、架构决策、开发规范等)
|
|
53
|
+
- global_long_term: 保存通用的信息、用户偏好、知识或方法(如技术知识、最佳实践、用户习惯等)
|
|
54
|
+
|
|
55
|
+
第二步:分析任务解决方案
|
|
56
|
+
1. 检查现有工具或方法论是否已经可以完成该任务,如果可以,直接说明即可,无需生成新内容
|
|
48
57
|
2. 如果现有工具/方法论不足,评估当前任务是否可以通过编写新工具来自动化解决
|
|
49
58
|
3. 如果可以通过工具解决,请设计并提供工具代码
|
|
50
59
|
4. 如果无法通过编写通用工具完成,评估当前的执行流程是否可以总结为通用方法论
|
|
51
60
|
5. 如果以上都不可行,给出详细理由
|
|
52
|
-
|
|
61
|
+
|
|
62
|
+
请根据分析结果采取相应行动。
|
|
63
|
+
|
|
64
|
+
重要提示:每次只能执行一个操作!
|
|
65
|
+
- 如果有记忆需要保存,可以调用一次 save_memory 批量保存多条记忆
|
|
66
|
+
- 保存完所有记忆后,再进行工具/方法论的创建或说明
|
|
67
|
+
- 不要在一次响应中同时调用多个工具(如同时保存记忆和创建工具/方法论)
|
|
53
68
|
</request>
|
|
54
69
|
<evaluation_criteria>
|
|
55
70
|
现有资源评估:
|
|
@@ -66,7 +81,7 @@ TASK_ANALYSIS_PROMPT = f"""<task_analysis>
|
|
|
66
81
|
2. 方法论应该具备足够的通用性,可应用于同类问题
|
|
67
82
|
3. 特别注意用户在执行过程中提供的修正、反馈和改进建议
|
|
68
83
|
4. 如果用户明确指出了某个解决步骤的优化方向,这应该被纳入方法论
|
|
69
|
-
5.
|
|
84
|
+
5. 方法论应面向未来复用,总结“下次遇到同类问题应该如何处理”的通用流程与检查清单,避免局限于本次执行细节
|
|
70
85
|
</evaluation_criteria>
|
|
71
86
|
<tool_requirements>
|
|
72
87
|
工具代码要求:
|
|
@@ -113,11 +128,36 @@ TASK_ANALYSIS_PROMPT = f"""<task_analysis>
|
|
|
113
128
|
"stderr": f"操作失败: {{str(e)}}"
|
|
114
129
|
}}
|
|
115
130
|
```
|
|
131
|
+
4. **在工具中调用大模型**:如果工具需要调用大模型来完成子任务(例如,生成代码、分析文本等),为了避免干扰主对话流程,建议创建一个独立的大模型实例。
|
|
132
|
+
```python
|
|
133
|
+
# 通过 agent 实例获取模型配置
|
|
134
|
+
agent = args.get("agent")
|
|
135
|
+
if not agent:
|
|
136
|
+
return {{"success": False, "stderr": "Agent not found."}}
|
|
137
|
+
|
|
138
|
+
current_model = agent.model
|
|
139
|
+
platform_name = current_model.platform_name()
|
|
140
|
+
model_name = current_model.name()
|
|
141
|
+
|
|
142
|
+
# 创建独立的模型实例
|
|
143
|
+
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
144
|
+
llm = PlatformRegistry().create_platform(platform_name)
|
|
145
|
+
if not llm:
|
|
146
|
+
return {{"success": False, "stderr": f"Platform {{platform_name}} not found."}}
|
|
147
|
+
|
|
148
|
+
llm.set_model_name(model_name)
|
|
149
|
+
llm.set_suppress_output(True) # 工具内的调用通常不需要流式输出
|
|
150
|
+
|
|
151
|
+
# 使用新实例调用大模型
|
|
152
|
+
PrettyOutput.print("正在执行子任务...", OutputType.INFO)
|
|
153
|
+
response = llm.chat_until_success("你的提示词")
|
|
154
|
+
PrettyOutput.print("子任务完成", OutputType.SUCCESS)
|
|
155
|
+
```
|
|
116
156
|
</tool_requirements>
|
|
117
157
|
<methodology_requirements>
|
|
118
158
|
方法论格式要求:
|
|
119
159
|
1. 问题重述: 简明扼要的问题归纳,不含特定细节
|
|
120
|
-
2.
|
|
160
|
+
2. 可复用解决流程: 面向“下次遇到同类问题”的步骤化方案(列出每步可用的工具),避免与本次特定上下文绑定
|
|
121
161
|
3. 注意事项: 执行中可能遇到的常见问题和注意点,尤其是用户指出的问题
|
|
122
162
|
4. 可选步骤: 对于有多种解决路径的问题,标注出可选步骤和适用场景
|
|
123
163
|
</methodology_requirements>
|
|
@@ -139,10 +179,7 @@ arguments:
|
|
|
139
179
|
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
140
180
|
class 工具名称:
|
|
141
181
|
name = "工具名称"
|
|
142
|
-
description = "Tool
|
|
143
|
-
Tool description
|
|
144
|
-
适用场景:1. 格式化文本; 2. 处理标题; 3. 标准化输出
|
|
145
|
-
\"\"\"
|
|
182
|
+
description = "Tool description"
|
|
146
183
|
parameters = {{
|
|
147
184
|
"type": "object",
|
|
148
185
|
"properties": {{
|
jarvis/jarvis_agent/protocols.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
from typing import Any, Protocol, Tuple
|
|
2
|
+
from typing import Any, Protocol, Tuple, runtime_checkable
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
@runtime_checkable
|
|
5
6
|
class OutputHandlerProtocol(Protocol):
|
|
6
7
|
"""
|
|
7
8
|
Defines the interface for an output handler, which is responsible for
|
|
@@ -28,3 +29,5 @@ class OutputHandlerProtocol(Protocol):
|
|
|
28
29
|
A tuple containing a boolean (whether to return) and the result.
|
|
29
30
|
"""
|
|
30
31
|
...
|
|
32
|
+
|
|
33
|
+
__all__ = ["OutputHandlerProtocol"]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, List, Tuple
|
|
5
|
+
|
|
6
|
+
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
7
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
8
|
+
from jarvis.jarvis_utils.tag import ct, ot
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RewriteFileHandler(OutputHandler):
|
|
12
|
+
"""
|
|
13
|
+
处理整文件重写指令的输出处理器。
|
|
14
|
+
|
|
15
|
+
指令格式:
|
|
16
|
+
<REWRITE file=文件路径>
|
|
17
|
+
新的文件完整内容
|
|
18
|
+
</REWRITE>
|
|
19
|
+
|
|
20
|
+
等价支持以下写法:
|
|
21
|
+
<REWRITE file=文件路径>
|
|
22
|
+
新的文件完整内容
|
|
23
|
+
</REWRITE>
|
|
24
|
+
|
|
25
|
+
说明:
|
|
26
|
+
- 该处理器用于完全重写文件内容,适用于新增文件或大范围改写
|
|
27
|
+
- 内部直接执行写入,提供失败回滚能力
|
|
28
|
+
- 支持同一响应中包含多个 REWRITE/REWRITE 块
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
# 允许 file 参数为单引号、双引号或无引号
|
|
33
|
+
self.rewrite_pattern_file = re.compile(
|
|
34
|
+
ot("REWRITE file=(?:'([^']+)'|\"([^\"]+)\"|([^>]+))")
|
|
35
|
+
+ r"\s*"
|
|
36
|
+
+ r"(.*?)"
|
|
37
|
+
+ r"\s*"
|
|
38
|
+
+ r"^"
|
|
39
|
+
+ ct("REWRITE"),
|
|
40
|
+
re.DOTALL | re.MULTILINE,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def name(self) -> str:
|
|
44
|
+
"""获取处理器名称,用于操作列表展示"""
|
|
45
|
+
return "REWRITE"
|
|
46
|
+
|
|
47
|
+
def prompt(self) -> str:
|
|
48
|
+
"""返回用户提示,描述使用方法与格式"""
|
|
49
|
+
return f"""文件重写指令格式:
|
|
50
|
+
{ot("REWRITE file=文件路径")}
|
|
51
|
+
新的文件完整内容
|
|
52
|
+
{ct("REWRITE")}
|
|
53
|
+
|
|
54
|
+
注意:
|
|
55
|
+
- {ot("REWRITE")}、{ct("REWRITE")} 必须出现在行首,否则不生效(会被忽略)
|
|
56
|
+
- 整文件重写会完全替换文件内容,如需局部修改请使用 PATCH 操作
|
|
57
|
+
- 该操作由处理器直接执行,具备失败回滚能力"""
|
|
58
|
+
|
|
59
|
+
def can_handle(self, response: str) -> bool:
|
|
60
|
+
"""判断响应中是否包含 REWRITE/REWRITE 指令"""
|
|
61
|
+
return bool(self.rewrite_pattern_file.search(response))
|
|
62
|
+
|
|
63
|
+
def handle(self, response: str, agent: Any) -> Tuple[bool, str]:
|
|
64
|
+
"""解析并执行整文件重写指令"""
|
|
65
|
+
rewrites = self._parse_rewrites(response)
|
|
66
|
+
if not rewrites:
|
|
67
|
+
return False, "未找到有效的文件重写指令"
|
|
68
|
+
|
|
69
|
+
# 记录 REWRITE 操作调用统计
|
|
70
|
+
try:
|
|
71
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
|
72
|
+
|
|
73
|
+
StatsManager.increment("rewrite_file", group="tool")
|
|
74
|
+
except Exception:
|
|
75
|
+
# 统计失败不影响主流程
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
results: List[str] = []
|
|
79
|
+
|
|
80
|
+
for file_path, content in rewrites:
|
|
81
|
+
abs_path = os.path.abspath(file_path)
|
|
82
|
+
original_content = None
|
|
83
|
+
processed = False
|
|
84
|
+
try:
|
|
85
|
+
file_exists = os.path.exists(abs_path)
|
|
86
|
+
if file_exists:
|
|
87
|
+
with open(abs_path, "r", encoding="utf-8") as rf:
|
|
88
|
+
original_content = rf.read()
|
|
89
|
+
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
|
|
90
|
+
with open(abs_path, "w", encoding="utf-8") as wf:
|
|
91
|
+
wf.write(content)
|
|
92
|
+
processed = True
|
|
93
|
+
results.append(f"✅ 文件 {abs_path} 重写成功")
|
|
94
|
+
# 记录成功处理的文件(使用绝对路径)
|
|
95
|
+
if agent:
|
|
96
|
+
files = agent.get_user_data("files")
|
|
97
|
+
if files:
|
|
98
|
+
if abs_path not in files:
|
|
99
|
+
files.append(abs_path)
|
|
100
|
+
else:
|
|
101
|
+
files = [abs_path]
|
|
102
|
+
agent.set_user_data("files", files)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
# 回滚已修改内容
|
|
105
|
+
try:
|
|
106
|
+
if processed:
|
|
107
|
+
if original_content is None:
|
|
108
|
+
if os.path.exists(abs_path):
|
|
109
|
+
os.remove(abs_path)
|
|
110
|
+
else:
|
|
111
|
+
with open(abs_path, "w", encoding="utf-8") as wf:
|
|
112
|
+
wf.write(original_content)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
PrettyOutput.print(f"文件重写失败: {str(e)}", OutputType.ERROR)
|
|
116
|
+
results.append(f"❌ 文件 {abs_path} 重写失败: {str(e)}")
|
|
117
|
+
|
|
118
|
+
summary = "\n".join(results)
|
|
119
|
+
# 按现有 EditFileHandler 约定,始终返回 (False, summary) 以继续主循环
|
|
120
|
+
return False, summary
|
|
121
|
+
|
|
122
|
+
def _parse_rewrites(self, response: str) -> List[Tuple[str, str]]:
|
|
123
|
+
"""
|
|
124
|
+
解析响应中的 REWRITE/REWRITE 指令块。
|
|
125
|
+
返回列表 [(file_path, content), ...],按在响应中的出现顺序排序
|
|
126
|
+
"""
|
|
127
|
+
items: List[Tuple[str, str]] = []
|
|
128
|
+
matches: List[Tuple[int, Any]] = []
|
|
129
|
+
for m in self.rewrite_pattern_file.finditer(response):
|
|
130
|
+
matches.append((m.start(), m))
|
|
131
|
+
|
|
132
|
+
# 按出现顺序排序
|
|
133
|
+
matches.sort(key=lambda x: x[0])
|
|
134
|
+
|
|
135
|
+
for _, m in matches:
|
|
136
|
+
file_path = m.group(1) or m.group(2) or m.group(3) or ""
|
|
137
|
+
file_path = file_path.strip()
|
|
138
|
+
content = m.group(4)
|
|
139
|
+
if file_path:
|
|
140
|
+
items.append((file_path, content))
|
|
141
|
+
return items
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
AgentRunLoop: 承载 Agent 的主运行循环逻辑。
|
|
4
|
+
|
|
5
|
+
阶段一目标(最小变更):
|
|
6
|
+
- 复制现有 _main_loop 逻辑到独立类,使用传入的 agent 实例进行委派调用
|
|
7
|
+
- 暂不变更外部调用入口,后续在 Agent._main_loop 中委派到该类
|
|
8
|
+
- 保持与现有异常处理、工具调用、用户交互完全一致
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
15
|
+
from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL
|
|
16
|
+
from jarvis.jarvis_agent.utils import join_prompts, is_auto_complete, normalize_next_action
|
|
17
|
+
from jarvis.jarvis_utils.config import get_auto_summary_rounds
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
# 仅用于类型标注,避免运行时循环依赖
|
|
21
|
+
from . import Agent # noqa: F401
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentRunLoop:
|
|
25
|
+
def __init__(self, agent: "Agent") -> None:
|
|
26
|
+
self.agent = agent
|
|
27
|
+
self.conversation_rounds = 0
|
|
28
|
+
self.tool_reminder_rounds = int(os.environ.get("JARVIS_TOOL_REMINDER_ROUNDS", 20))
|
|
29
|
+
# 基于轮次的自动总结阈值:优先使用 Agent 入参,否则回落到全局配置(默认20轮)
|
|
30
|
+
self.auto_summary_rounds = (
|
|
31
|
+
self.agent.auto_summary_rounds
|
|
32
|
+
if getattr(self.agent, "auto_summary_rounds", None) is not None
|
|
33
|
+
else get_auto_summary_rounds()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def run(self) -> Any:
|
|
37
|
+
"""主运行循环(委派到传入的 agent 实例的方法与属性)"""
|
|
38
|
+
run_input_handlers = True
|
|
39
|
+
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
self.conversation_rounds += 1
|
|
43
|
+
if self.conversation_rounds % self.tool_reminder_rounds == 0:
|
|
44
|
+
self.agent.session.addon_prompt = join_prompts(
|
|
45
|
+
[self.agent.session.addon_prompt, self.agent.get_tool_usage_prompt()]
|
|
46
|
+
)
|
|
47
|
+
# 基于轮次的自动总结判断:达到阈值后执行一次总结与历史清理
|
|
48
|
+
if self.conversation_rounds >= self.auto_summary_rounds:
|
|
49
|
+
summary_text = self.agent._summarize_and_clear_history()
|
|
50
|
+
if summary_text:
|
|
51
|
+
# 将摘要作为下一轮的附加提示加入,从而维持上下文连续性
|
|
52
|
+
self.agent.session.addon_prompt = join_prompts(
|
|
53
|
+
[self.agent.session.addon_prompt, summary_text]
|
|
54
|
+
)
|
|
55
|
+
# 重置轮次计数与对话长度计数器,开始新一轮周期
|
|
56
|
+
self.conversation_rounds = 0
|
|
57
|
+
self.agent.session.conversation_length = 0
|
|
58
|
+
|
|
59
|
+
ag = self.agent
|
|
60
|
+
|
|
61
|
+
# 更新输入处理器标志
|
|
62
|
+
if ag.run_input_handlers_next_turn:
|
|
63
|
+
run_input_handlers = True
|
|
64
|
+
ag.run_input_handlers_next_turn = False
|
|
65
|
+
|
|
66
|
+
# 首次运行初始化
|
|
67
|
+
if ag.first:
|
|
68
|
+
ag._first_run()
|
|
69
|
+
|
|
70
|
+
# 调用模型获取响应
|
|
71
|
+
current_response = ag._call_model(
|
|
72
|
+
ag.session.prompt, True, run_input_handlers
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
ag.session.prompt = ""
|
|
76
|
+
run_input_handlers = False
|
|
77
|
+
|
|
78
|
+
# 处理中断
|
|
79
|
+
interrupt_result = ag._handle_run_interrupt(current_response)
|
|
80
|
+
if (
|
|
81
|
+
isinstance(interrupt_result, Enum)
|
|
82
|
+
and getattr(interrupt_result, "value", None) == "skip_turn"
|
|
83
|
+
):
|
|
84
|
+
# 中断处理器请求跳过本轮剩余部分,直接开始下一次循环
|
|
85
|
+
continue
|
|
86
|
+
elif interrupt_result is not None and not isinstance(interrupt_result, Enum):
|
|
87
|
+
# 中断处理器返回了最终结果,任务结束
|
|
88
|
+
return interrupt_result
|
|
89
|
+
|
|
90
|
+
# 处理工具调用
|
|
91
|
+
# 广播工具调用前事件(不影响主流程)
|
|
92
|
+
try:
|
|
93
|
+
ag.event_bus.emit(
|
|
94
|
+
BEFORE_TOOL_CALL,
|
|
95
|
+
agent=ag,
|
|
96
|
+
current_response=current_response,
|
|
97
|
+
)
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
need_return, tool_prompt = ag._call_tools(current_response)
|
|
101
|
+
|
|
102
|
+
# 如果工具要求立即返回结果(例如 SEND_MESSAGE 需要将字典返回给上层),直接返回该结果
|
|
103
|
+
if need_return:
|
|
104
|
+
return tool_prompt
|
|
105
|
+
|
|
106
|
+
# 将上一个提示和工具提示安全地拼接起来(仅当工具结果为字符串时)
|
|
107
|
+
safe_tool_prompt = tool_prompt if isinstance(tool_prompt, str) else ""
|
|
108
|
+
ag.session.prompt = join_prompts([ag.session.prompt, safe_tool_prompt])
|
|
109
|
+
|
|
110
|
+
# 广播工具调用后的事件(不影响主流程)
|
|
111
|
+
try:
|
|
112
|
+
ag.event_bus.emit(
|
|
113
|
+
AFTER_TOOL_CALL,
|
|
114
|
+
agent=ag,
|
|
115
|
+
current_response=current_response,
|
|
116
|
+
need_return=need_return,
|
|
117
|
+
tool_prompt=tool_prompt,
|
|
118
|
+
)
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
# 检查是否需要继续
|
|
123
|
+
if ag.session.prompt or ag.session.addon_prompt:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# 检查自动完成
|
|
127
|
+
if ag.auto_complete and is_auto_complete(current_response):
|
|
128
|
+
# 先运行_complete_task,触发记忆整理/事件等副作用,再决定返回值
|
|
129
|
+
result = ag._complete_task(auto_completed=True)
|
|
130
|
+
# 若不需要summary,则将最后一条LLM输出作为返回值
|
|
131
|
+
if not getattr(ag, "need_summary", True):
|
|
132
|
+
return current_response
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
# 获取下一步用户输入
|
|
136
|
+
next_action = ag._get_next_user_action()
|
|
137
|
+
action = normalize_next_action(next_action)
|
|
138
|
+
if action == "continue":
|
|
139
|
+
run_input_handlers = True
|
|
140
|
+
continue
|
|
141
|
+
elif action == "complete":
|
|
142
|
+
return ag._complete_task(auto_completed=False)
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
PrettyOutput.print(f"任务失败: {str(e)}", OutputType.ERROR)
|
|
146
|
+
return f"Task failed: {str(e)}"
|
|
@@ -34,14 +34,6 @@ class SessionManager:
|
|
|
34
34
|
"""Sets the addon prompt for the next model call."""
|
|
35
35
|
self.addon_prompt = addon_prompt
|
|
36
36
|
|
|
37
|
-
def clear(self):
|
|
38
|
-
"""
|
|
39
|
-
Clears the current conversation history, prompt, and length counter.
|
|
40
|
-
"""
|
|
41
|
-
self.model.reset()
|
|
42
|
-
self.conversation_length = 0
|
|
43
|
-
self.prompt = ""
|
|
44
|
-
|
|
45
37
|
def save_session(self) -> bool:
|
|
46
38
|
"""Saves the current session state to a file."""
|
|
47
39
|
session_dir = os.path.join(os.getcwd(), ".jarvis")
|
|
@@ -75,10 +67,18 @@ class SessionManager:
|
|
|
75
67
|
return True
|
|
76
68
|
return False
|
|
77
69
|
|
|
78
|
-
def clear_history(self):
|
|
70
|
+
def clear_history(self) -> None:
|
|
79
71
|
"""
|
|
80
72
|
Clears conversation history but keeps the system prompt by resetting the model state.
|
|
81
73
|
"""
|
|
82
74
|
self.prompt = ""
|
|
83
75
|
self.model.reset()
|
|
84
76
|
self.conversation_length = 0
|
|
77
|
+
|
|
78
|
+
def clear(self) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Clears the session state, resetting prompt and conversation length while
|
|
81
|
+
preserving user_data. This method is an alias of clear_history for backward
|
|
82
|
+
compatibility with existing tests and callers.
|
|
83
|
+
"""
|
|
84
|
+
self.clear_history()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""分享管理模块,负责工具和方法论的分享功能"""
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import List, Dict, Any, Set
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
from prompt_toolkit import prompt
|
|
9
|
+
|
|
10
|
+
from jarvis.jarvis_agent import OutputType, PrettyOutput, user_confirm
|
|
11
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_selection(selection_str: str, max_value: int) -> List[int]:
|
|
15
|
+
"""解析用户输入的选择字符串,支持逗号分隔和范围选择
|
|
16
|
+
|
|
17
|
+
例如: "1,2,3,4-9,20" -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
|
|
18
|
+
"""
|
|
19
|
+
selected: Set[int] = set()
|
|
20
|
+
parts = selection_str.split(",")
|
|
21
|
+
|
|
22
|
+
for part in parts:
|
|
23
|
+
part = part.strip()
|
|
24
|
+
if "-" in part:
|
|
25
|
+
# 处理范围选择
|
|
26
|
+
try:
|
|
27
|
+
start_str, end_str = part.split("-")
|
|
28
|
+
start_num = int(start_str.strip())
|
|
29
|
+
end_num = int(end_str.strip())
|
|
30
|
+
if 1 <= start_num <= max_value and 1 <= end_num <= max_value:
|
|
31
|
+
selected.update(range(start_num, end_num + 1))
|
|
32
|
+
except ValueError:
|
|
33
|
+
continue
|
|
34
|
+
else:
|
|
35
|
+
# 处理单个数字
|
|
36
|
+
try:
|
|
37
|
+
num = int(part)
|
|
38
|
+
if 1 <= num <= max_value:
|
|
39
|
+
selected.add(num)
|
|
40
|
+
except ValueError:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
return sorted(list(selected))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ShareManager(ABC):
|
|
47
|
+
"""分享管理器基类"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, central_repo_url: str, repo_name: str):
|
|
50
|
+
self.central_repo_url = central_repo_url
|
|
51
|
+
self.repo_name = repo_name
|
|
52
|
+
# 支持将中心仓库配置为本地目录(含git子路径)
|
|
53
|
+
expanded = os.path.expanduser(os.path.expandvars(central_repo_url))
|
|
54
|
+
if os.path.isdir(expanded):
|
|
55
|
+
# 直接使用本地目录作为中心仓库路径(支持git仓库子目录)
|
|
56
|
+
self.repo_path = expanded
|
|
57
|
+
else:
|
|
58
|
+
# 仍按原逻辑使用数据目录中的克隆路径
|
|
59
|
+
self.repo_path = os.path.join(get_data_dir(), repo_name)
|
|
60
|
+
|
|
61
|
+
def update_central_repo(self) -> None:
|
|
62
|
+
"""克隆或更新中心仓库"""
|
|
63
|
+
if not os.path.exists(self.repo_path):
|
|
64
|
+
PrettyOutput.print(
|
|
65
|
+
f"正在克隆中心{self.get_resource_type()}仓库...", OutputType.INFO
|
|
66
|
+
)
|
|
67
|
+
subprocess.run(
|
|
68
|
+
["git", "clone", self.central_repo_url, self.repo_path], check=True
|
|
69
|
+
)
|
|
70
|
+
# 检查并添加.gitignore文件
|
|
71
|
+
gitignore_path = os.path.join(self.repo_path, ".gitignore")
|
|
72
|
+
modified = False
|
|
73
|
+
if not os.path.exists(gitignore_path):
|
|
74
|
+
with open(gitignore_path, "w") as f:
|
|
75
|
+
f.write("__pycache__/\n")
|
|
76
|
+
modified = True
|
|
77
|
+
else:
|
|
78
|
+
with open(gitignore_path, "r+") as f:
|
|
79
|
+
content = f.read()
|
|
80
|
+
if "__pycache__" not in content:
|
|
81
|
+
f.write("\n__pycache__/\n")
|
|
82
|
+
modified = True
|
|
83
|
+
|
|
84
|
+
if modified:
|
|
85
|
+
subprocess.run(
|
|
86
|
+
["git", "add", ".gitignore"], cwd=self.repo_path, check=True
|
|
87
|
+
)
|
|
88
|
+
subprocess.run(
|
|
89
|
+
["git", "commit", "-m", "chore: add __pycache__ to .gitignore"],
|
|
90
|
+
cwd=self.repo_path,
|
|
91
|
+
check=True,
|
|
92
|
+
)
|
|
93
|
+
subprocess.run(["git", "push"], cwd=self.repo_path, check=True)
|
|
94
|
+
else:
|
|
95
|
+
PrettyOutput.print(
|
|
96
|
+
f"正在更新中心{self.get_resource_type()}仓库...", OutputType.INFO
|
|
97
|
+
)
|
|
98
|
+
# 检查是否是空仓库
|
|
99
|
+
try:
|
|
100
|
+
# 先尝试获取远程分支信息
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
["git", "ls-remote", "--heads", "origin"],
|
|
103
|
+
cwd=self.repo_path,
|
|
104
|
+
capture_output=True,
|
|
105
|
+
text=True,
|
|
106
|
+
check=True,
|
|
107
|
+
)
|
|
108
|
+
# 如果有远程分支,执行pull
|
|
109
|
+
if result.stdout.strip():
|
|
110
|
+
# 检查是否有未提交的更改
|
|
111
|
+
status_result = subprocess.run(
|
|
112
|
+
["git", "status", "--porcelain"],
|
|
113
|
+
cwd=self.repo_path,
|
|
114
|
+
capture_output=True,
|
|
115
|
+
text=True,
|
|
116
|
+
check=True,
|
|
117
|
+
)
|
|
118
|
+
if status_result.stdout:
|
|
119
|
+
if user_confirm(
|
|
120
|
+
f"检测到中心{self.get_resource_type()}仓库 '{self.repo_name}' 存在未提交的更改,是否放弃这些更改并更新?"
|
|
121
|
+
):
|
|
122
|
+
subprocess.run(
|
|
123
|
+
["git", "checkout", "."], cwd=self.repo_path, check=True
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
PrettyOutput.print(
|
|
127
|
+
f"跳过更新 '{self.repo_name}' 以保留未提交的更改。",
|
|
128
|
+
OutputType.INFO,
|
|
129
|
+
)
|
|
130
|
+
return
|
|
131
|
+
subprocess.run(["git", "pull"], cwd=self.repo_path, check=True)
|
|
132
|
+
else:
|
|
133
|
+
PrettyOutput.print(
|
|
134
|
+
f"中心{self.get_resource_type()}仓库是空的,将初始化为新仓库",
|
|
135
|
+
OutputType.INFO,
|
|
136
|
+
)
|
|
137
|
+
except subprocess.CalledProcessError:
|
|
138
|
+
# 如果命令失败,可能是网络问题或其他错误
|
|
139
|
+
PrettyOutput.print("无法连接到远程仓库,将跳过更新", OutputType.WARNING)
|
|
140
|
+
|
|
141
|
+
def commit_and_push(self, count: int) -> None:
|
|
142
|
+
"""提交并推送更改"""
|
|
143
|
+
PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
|
|
144
|
+
subprocess.run(["git", "add", "."], cwd=self.repo_path, check=True)
|
|
145
|
+
|
|
146
|
+
commit_msg = f"Add {count} {self.get_resource_type()}(s) from local collection"
|
|
147
|
+
subprocess.run(
|
|
148
|
+
["git", "commit", "-m", commit_msg], cwd=self.repo_path, check=True
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
|
|
152
|
+
# 检查是否需要设置上游分支(空仓库的情况)
|
|
153
|
+
try:
|
|
154
|
+
# 先尝试普通推送
|
|
155
|
+
subprocess.run(["git", "push"], cwd=self.repo_path, check=True)
|
|
156
|
+
except subprocess.CalledProcessError:
|
|
157
|
+
# 如果失败,可能是空仓库,尝试设置上游分支
|
|
158
|
+
try:
|
|
159
|
+
subprocess.run(
|
|
160
|
+
["git", "push", "-u", "origin", "main"],
|
|
161
|
+
cwd=self.repo_path,
|
|
162
|
+
check=True,
|
|
163
|
+
)
|
|
164
|
+
except subprocess.CalledProcessError:
|
|
165
|
+
# 如果main分支不存在,尝试master分支
|
|
166
|
+
subprocess.run(
|
|
167
|
+
["git", "push", "-u", "origin", "master"],
|
|
168
|
+
cwd=self.repo_path,
|
|
169
|
+
check=True,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def select_resources(self, resources: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
173
|
+
"""让用户选择要分享的资源"""
|
|
174
|
+
# 显示可选的资源
|
|
175
|
+
resource_list = [
|
|
176
|
+
f"\n可分享的{self.get_resource_type()}(已排除中心仓库中已有的):"
|
|
177
|
+
]
|
|
178
|
+
for i, resource in enumerate(resources, 1):
|
|
179
|
+
resource_list.append(f"[{i}] {self.format_resource_display(resource)}")
|
|
180
|
+
|
|
181
|
+
# 一次性打印所有资源
|
|
182
|
+
PrettyOutput.print("\n".join(resource_list), OutputType.INFO)
|
|
183
|
+
|
|
184
|
+
# 让用户选择
|
|
185
|
+
while True:
|
|
186
|
+
try:
|
|
187
|
+
choice_str = prompt(
|
|
188
|
+
f"\n请选择要分享的{self.get_resource_type()}编号(支持格式: 1,2,3,4-9,20 或 all):"
|
|
189
|
+
).strip()
|
|
190
|
+
if choice_str == "0":
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
if choice_str.lower() == "all":
|
|
194
|
+
return resources
|
|
195
|
+
else:
|
|
196
|
+
selected_indices = parse_selection(choice_str, len(resources))
|
|
197
|
+
if not selected_indices:
|
|
198
|
+
PrettyOutput.print("无效的选择", OutputType.WARNING)
|
|
199
|
+
continue
|
|
200
|
+
return [resources[i - 1] for i in selected_indices]
|
|
201
|
+
|
|
202
|
+
except ValueError:
|
|
203
|
+
PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
|
|
204
|
+
|
|
205
|
+
@abstractmethod
|
|
206
|
+
def get_resource_type(self) -> str:
|
|
207
|
+
"""获取资源类型名称"""
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
@abstractmethod
|
|
211
|
+
def format_resource_display(self, resource: Dict[str, Any]) -> str:
|
|
212
|
+
"""格式化资源显示"""
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
@abstractmethod
|
|
216
|
+
def get_existing_resources(self) -> Any:
|
|
217
|
+
"""获取中心仓库中已有的资源"""
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
@abstractmethod
|
|
221
|
+
def get_local_resources(self) -> List[Dict[str, Any]]:
|
|
222
|
+
"""获取本地资源"""
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
@abstractmethod
|
|
226
|
+
def share_resources(self, resources: List[Dict[str, Any]]) -> List[str]:
|
|
227
|
+
"""分享资源到中心仓库"""
|
|
228
|
+
pass
|