jarvis-ai-assistant 0.3.29__py3-none-any.whl → 0.3.31__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 +116 -9
- jarvis/jarvis_agent/edit_file_handler.py +330 -83
- jarvis/jarvis_agent/run_loop.py +1 -3
- jarvis/jarvis_code_agent/code_agent.py +91 -8
- jarvis/jarvis_data/config_schema.json +46 -4
- jarvis/jarvis_tools/edit_file.py +39 -10
- jarvis/jarvis_tools/generate_new_tool.py +13 -2
- jarvis/jarvis_tools/read_code.py +12 -11
- jarvis/jarvis_tools/registry.py +16 -15
- jarvis/jarvis_utils/config.py +31 -0
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/git_utils.py +34 -18
- jarvis/jarvis_utils/utils.py +74 -18
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/RECORD +20 -20
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.29.dist-info → jarvis_ai_assistant-0.3.31.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@
|
|
7
7
|
"JARVIS_MCP": {
|
8
8
|
"type": "array",
|
9
9
|
"description": "MCP工具配置列表",
|
10
|
+
"default": [],
|
10
11
|
"items": {
|
11
12
|
"type": "object",
|
12
13
|
"oneOf": [
|
@@ -182,11 +183,22 @@
|
|
182
183
|
"description": "执行工具前是否需要确认",
|
183
184
|
"default": false
|
184
185
|
},
|
186
|
+
"JARVIS_TOOL_FILTER_THRESHOLD": {
|
187
|
+
"type": "number",
|
188
|
+
"description": "AI工具筛选阈值:当可用工具数量超过此值时触发AI筛选",
|
189
|
+
"default": 30
|
190
|
+
},
|
185
191
|
"JARVIS_CONFIRM_BEFORE_APPLY_PATCH": {
|
186
192
|
"type": "boolean",
|
187
193
|
"description": "应用补丁前是否需要确认",
|
188
194
|
"default": false
|
189
195
|
},
|
196
|
+
"JARVIS_PATCH_FORMAT": {
|
197
|
+
"type": "string",
|
198
|
+
"enum": ["all", "search", "search_range"],
|
199
|
+
"description": "补丁格式处理模式:all 同时支持 SEARCH 与 SEARCH_START/SEARCH_END;search 仅允许精确片段匹配;search_range 仅允许范围匹配。",
|
200
|
+
"default": "all"
|
201
|
+
},
|
190
202
|
"JARVIS_DATA_PATH": {
|
191
203
|
"type": "string",
|
192
204
|
"description": "Jarvis数据存储目录路径",
|
@@ -195,7 +207,7 @@
|
|
195
207
|
"JARVIS_PRETTY_OUTPUT": {
|
196
208
|
"type": "boolean",
|
197
209
|
"description": "是否启用美化输出",
|
198
|
-
"default":
|
210
|
+
"default": true
|
199
211
|
},
|
200
212
|
"JARVIS_USE_METHODOLOGY": {
|
201
213
|
"type": "boolean",
|
@@ -247,6 +259,14 @@
|
|
247
259
|
},
|
248
260
|
"default": []
|
249
261
|
},
|
262
|
+
"JARVIS_AFTER_TOOL_CALL_CB_DIRS": {
|
263
|
+
"type": "array",
|
264
|
+
"description": "工具调用后回调函数实现目录",
|
265
|
+
"items": {
|
266
|
+
"type": "string"
|
267
|
+
},
|
268
|
+
"default": []
|
269
|
+
},
|
250
270
|
"JARVIS_CENTRAL_METHODOLOGY_REPO": {
|
251
271
|
"type": "string",
|
252
272
|
"description": "中心方法论Git仓库地址,该仓库会自动添加到方法论加载路径中",
|
@@ -262,6 +282,11 @@
|
|
262
282
|
"description": "是否打印提示",
|
263
283
|
"default": false
|
264
284
|
},
|
285
|
+
"JARVIS_PRINT_ERROR_TRACEBACK": {
|
286
|
+
"type": "boolean",
|
287
|
+
"description": "是否在错误输出时打印回溯调用链",
|
288
|
+
"default": false
|
289
|
+
},
|
265
290
|
"JARVIS_ENABLE_STATIC_ANALYSIS": {
|
266
291
|
"type": "boolean",
|
267
292
|
"description": "是否启用静态代码分析",
|
@@ -270,7 +295,7 @@
|
|
270
295
|
"JARVIS_FORCE_SAVE_MEMORY": {
|
271
296
|
"type": "boolean",
|
272
297
|
"description": "是否强制保存记忆",
|
273
|
-
"default":
|
298
|
+
"default": false
|
274
299
|
},
|
275
300
|
"JARVIS_ENABLE_GIT_JCA_SWITCH": {
|
276
301
|
"type": "boolean",
|
@@ -333,7 +358,23 @@
|
|
333
358
|
"JARVIS_RAG_GROUPS": {
|
334
359
|
"type": "array",
|
335
360
|
"description": "预定义的RAG配置组",
|
336
|
-
"default": [
|
361
|
+
"default": [
|
362
|
+
{
|
363
|
+
"text": {
|
364
|
+
"embedding_model": "BAAI/bge-m3",
|
365
|
+
"rerank_model": "BAAI/bge-reranker-v2-m3",
|
366
|
+
"use_bm25": true,
|
367
|
+
"use_rerank": true
|
368
|
+
}
|
369
|
+
},
|
370
|
+
{
|
371
|
+
"code": {
|
372
|
+
"embedding_model": "Qodo/Qodo-Embed-1-1.5B",
|
373
|
+
"use_bm25": false,
|
374
|
+
"use_rerank": false
|
375
|
+
}
|
376
|
+
}
|
377
|
+
],
|
337
378
|
"items": {
|
338
379
|
"type": "object",
|
339
380
|
"additionalProperties": {
|
@@ -413,7 +454,8 @@
|
|
413
454
|
"required": [
|
414
455
|
"template"
|
415
456
|
]
|
416
|
-
}
|
457
|
+
},
|
458
|
+
"default": {}
|
417
459
|
},
|
418
460
|
"OPENAI_API_KEY": {
|
419
461
|
"type": "string",
|
jarvis/jarvis_tools/edit_file.py
CHANGED
@@ -27,15 +27,24 @@ class FileSearchReplaceTool:
|
|
27
27
|
|
28
28
|
## 基本使用
|
29
29
|
1. 指定需要修改的文件路径(单个或多个)
|
30
|
-
2.
|
31
|
-
-
|
32
|
-
|
33
|
-
|
30
|
+
2. 提供一组或多组修改,每个修改支持两种格式:
|
31
|
+
- 单点替换:
|
32
|
+
- reason: 修改原因描述
|
33
|
+
- SEARCH: 需要查找的原始代码(必须包含足够上下文)
|
34
|
+
- REPLACE: 替换后的新代码
|
35
|
+
- 区间替换:
|
36
|
+
- reason: 修改原因描述
|
37
|
+
- SEARCH_START: 起始标记(包含在替换范围内)
|
38
|
+
- SEARCH_END: 结束标记(包含在替换范围内)
|
39
|
+
- REPLACE: 替换后的新代码
|
40
|
+
- RANGE: 可选的行号范围 'start-end' (1-based, 闭区间), 用于限定匹配范围
|
34
41
|
|
35
42
|
## 核心原则
|
36
43
|
1. **精准修改**: 只修改必要的代码部分,保持其他部分不变
|
37
44
|
2. **最小补丁原则**: 生成最小范围的补丁,包含必要的上下文
|
38
|
-
3. **唯一匹配**:
|
45
|
+
3. **唯一匹配**:
|
46
|
+
- 单点替换:确保 SEARCH 在文件中唯一匹配
|
47
|
+
- 区间替换:确保 SEARCH_START 在文件中唯一匹配,且在其后 SEARCH_END 也唯一匹配
|
39
48
|
4. **格式保持**: 严格保持原始代码的格式风格
|
40
49
|
5. **部分成功**: 支持多个文件编辑,允许部分文件编辑成功
|
41
50
|
|
@@ -62,12 +71,24 @@ class FileSearchReplaceTool:
|
|
62
71
|
},
|
63
72
|
"SEARCH": {
|
64
73
|
"type": "string",
|
65
|
-
"description": "
|
74
|
+
"description": "需要查找的原始代码(单点替换模式)",
|
75
|
+
},
|
76
|
+
"SEARCH_START": {
|
77
|
+
"type": "string",
|
78
|
+
"description": "区间替换的起始标记(包含在替换范围内)",
|
79
|
+
},
|
80
|
+
"SEARCH_END": {
|
81
|
+
"type": "string",
|
82
|
+
"description": "区间替换的结束标记(包含在替换范围内)",
|
66
83
|
},
|
67
84
|
"REPLACE": {
|
68
85
|
"type": "string",
|
69
86
|
"description": "替换后的新代码",
|
70
87
|
},
|
88
|
+
"RANGE": {
|
89
|
+
"type": "string",
|
90
|
+
"description": "行号范围 'start-end'(1-based,闭区间),可选,仅用于区间替换模式,用于限定匹配与替换的行号范围",
|
91
|
+
},
|
71
92
|
},
|
72
93
|
},
|
73
94
|
},
|
@@ -93,10 +114,18 @@ class FileSearchReplaceTool:
|
|
93
114
|
args: 包含以下键的字典:
|
94
115
|
- files: 文件列表,每个文件包含(必填):
|
95
116
|
- path: 要修改的文件路径
|
96
|
-
- changes:
|
97
|
-
|
98
|
-
|
99
|
-
|
117
|
+
- changes: 修改列表,每个修改支持两种格式:
|
118
|
+
1) 单点替换:
|
119
|
+
- reason: 修改原因描述
|
120
|
+
- SEARCH: 需要查找的原始代码(必须包含足够上下文)
|
121
|
+
- REPLACE: 替换后的新代码
|
122
|
+
2) 区间替换:
|
123
|
+
- reason: 修改原因描述
|
124
|
+
- SEARCH_START: 起始标记(包含在替换范围内)
|
125
|
+
- SEARCH_END: 结束标记(包含在替换范围内)
|
126
|
+
- REPLACE: 替换后的新代码
|
127
|
+
通用可选项:
|
128
|
+
- RANGE: 形如 'start-end'(1-based,闭区间),仅用于区间替换模式。当提供时仅在该行号范围内执行匹配与替换;省略则在整个文件范围内处理
|
100
129
|
|
101
130
|
返回:
|
102
131
|
Dict[str, Any] 包含:
|
@@ -173,9 +173,20 @@ class generate_new_tool:
|
|
173
173
|
__import__(pkg)
|
174
174
|
except ImportError:
|
175
175
|
|
176
|
-
import subprocess
|
176
|
+
import subprocess, sys, os
|
177
|
+
from shutil import which as _which
|
178
|
+
# 优先使用 uv 安装(先查 venv 内 uv,再查 PATH 中 uv),否则回退到 python -m pip
|
179
|
+
if sys.platform == "win32":
|
180
|
+
venv_uv = os.path.join(sys.prefix, "Scripts", "uv.exe")
|
181
|
+
else:
|
182
|
+
venv_uv = os.path.join(sys.prefix, "bin", "uv")
|
183
|
+
uv_executable = venv_uv if os.path.exists(venv_uv) else (_which("uv") or None)
|
184
|
+
if uv_executable:
|
185
|
+
install_cmd = [uv_executable, "pip", "install", pkg]
|
186
|
+
else:
|
187
|
+
install_cmd = [sys.executable, "-m", "pip", "install", pkg]
|
188
|
+
subprocess.run(install_cmd, check=True)
|
177
189
|
|
178
|
-
subprocess.run(["pip", "install", pkg], check=True)
|
179
190
|
|
180
191
|
except Exception as e:
|
181
192
|
PrettyOutput.print(f"依赖检查/安装失败: {str(e)}", OutputType.WARNING)
|
jarvis/jarvis_tools/read_code.py
CHANGED
@@ -62,10 +62,9 @@ class ReadCodeTool:
|
|
62
62
|
}
|
63
63
|
|
64
64
|
# 读取文件内容
|
65
|
+
# 第一遍流式读取,仅统计总行数,避免一次性读入内存
|
65
66
|
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
|
66
|
-
|
67
|
-
|
68
|
-
total_lines = len(lines)
|
67
|
+
total_lines = sum(1 for _ in f)
|
69
68
|
|
70
69
|
# 处理空文件情况
|
71
70
|
if total_lines == 0:
|
@@ -99,14 +98,16 @@ class ReadCodeTool:
|
|
99
98
|
"stderr": f"无效的行范围 [{start_line}-{end_line}] (总行数: {total_lines})",
|
100
99
|
}
|
101
100
|
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
101
|
+
# 添加行号并构建输出内容(第二遍流式读取,仅提取范围行)
|
102
|
+
selected_items = []
|
103
|
+
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
|
104
|
+
for i, line in enumerate(f, start=1):
|
105
|
+
if i < start_line:
|
106
|
+
continue
|
107
|
+
if i > end_line:
|
108
|
+
break
|
109
|
+
selected_items.append((i, line))
|
110
|
+
numbered_content = "".join(f"{i:4d}:{line}" for i, line in selected_items)
|
110
111
|
|
111
112
|
# 构建输出格式
|
112
113
|
output = (
|
jarvis/jarvis_tools/registry.py
CHANGED
@@ -51,6 +51,7 @@ arguments:
|
|
51
51
|
- 完全按照上述格式
|
52
52
|
- 使用正确的YAML格式,2个空格作为缩进
|
53
53
|
- 包含所有必需参数
|
54
|
+
- {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 必须出现在行首
|
54
55
|
</rule>
|
55
56
|
|
56
57
|
<rule>
|
@@ -101,6 +102,7 @@ arguments:
|
|
101
102
|
- 创建虚构对话
|
102
103
|
- 在没有所需信息的情况下继续
|
103
104
|
- yaml 格式错误
|
105
|
+
- {ot("TOOL_CALL")} 和 {ct("TOOL_CALL")} 没有出现在行首
|
104
106
|
</common_errors>
|
105
107
|
</tool_system_guide>
|
106
108
|
"""
|
@@ -121,7 +123,8 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
121
123
|
return "TOOL_CALL"
|
122
124
|
|
123
125
|
def can_handle(self, response: str) -> bool:
|
124
|
-
|
126
|
+
# 仅当 {ot("TOOL_CALL")} 出现在行首时才认为可以处理
|
127
|
+
return re.search(rf'(?m){re.escape(ot("TOOL_CALL"))}', response) is not None
|
125
128
|
|
126
129
|
def prompt(self) -> str:
|
127
130
|
"""加载工具"""
|
@@ -608,11 +611,9 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
608
611
|
|
609
612
|
@staticmethod
|
610
613
|
def _has_tool_calls_block(content: str) -> bool:
|
611
|
-
"""
|
612
|
-
|
613
|
-
|
614
|
-
is not None
|
615
|
-
)
|
614
|
+
"""从内容中提取工具调用块(仅匹配行首标签)"""
|
615
|
+
pattern = rf'(?ms){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
616
|
+
return re.search(pattern, content) is not None
|
616
617
|
|
617
618
|
@staticmethod
|
618
619
|
def _extract_tool_calls(
|
@@ -633,22 +634,22 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
633
634
|
Exception: 如果工具调用缺少必要字段
|
634
635
|
"""
|
635
636
|
# 将内容拆分为行
|
636
|
-
|
637
|
-
|
638
|
-
)
|
637
|
+
pattern = rf'(?ms){re.escape(ot("TOOL_CALL"))}(.*?)^{re.escape(ct("TOOL_CALL"))}'
|
638
|
+
data = re.findall(pattern, content)
|
639
639
|
auto_completed = False
|
640
640
|
if not data:
|
641
|
-
# can_handle 确保 ot("TOOL_CALL")
|
642
|
-
#
|
643
|
-
|
644
|
-
|
641
|
+
# can_handle 确保 ot("TOOL_CALL") 在内容中(行首)。
|
642
|
+
# 如果数据为空,则表示行首的 ct("TOOL_CALL") 可能丢失。
|
643
|
+
has_open_at_bol = re.search(rf'(?m){re.escape(ot("TOOL_CALL"))}', content) is not None
|
644
|
+
has_close_at_bol = re.search(rf'(?m)^{re.escape(ct("TOOL_CALL"))}', content) is not None
|
645
|
+
if has_open_at_bol and not has_close_at_bol:
|
646
|
+
# 尝试通过附加结束标签来修复它(确保结束标签位于行首)
|
645
647
|
fixed_content = content.strip() + f"\n{ct('TOOL_CALL')}"
|
646
648
|
|
647
649
|
# 再次提取,并检查YAML是否有效
|
648
650
|
temp_data = re.findall(
|
649
|
-
|
651
|
+
pattern,
|
650
652
|
fixed_content,
|
651
|
-
re.DOTALL,
|
652
653
|
)
|
653
654
|
|
654
655
|
if temp_data:
|
jarvis/jarvis_utils/config.py
CHANGED
@@ -248,6 +248,23 @@ def is_confirm_before_apply_patch() -> bool:
|
|
248
248
|
return GLOBAL_CONFIG_DATA.get("JARVIS_CONFIRM_BEFORE_APPLY_PATCH", False)
|
249
249
|
|
250
250
|
|
251
|
+
def get_patch_format() -> str:
|
252
|
+
"""
|
253
|
+
获取补丁格式。
|
254
|
+
|
255
|
+
- "search": 仅使用精确匹配的 `SEARCH` 模式。此模式对能力较弱的模型更稳定,因为它要求代码片段完全匹配。
|
256
|
+
- "search_range": 仅使用 `SEARCH_START` 和 `SEARCH_END` 的范围匹配模式。此模式对能力较强的模型更灵活,因为它允许在代码块内部进行修改,而不要求整个块完全匹配。
|
257
|
+
- "all": 同时支持以上两种模式(默认)。
|
258
|
+
|
259
|
+
返回:
|
260
|
+
str: "all", "search", or "search_range"
|
261
|
+
"""
|
262
|
+
mode = GLOBAL_CONFIG_DATA.get("JARVIS_PATCH_FORMAT", "all")
|
263
|
+
if mode in ["all", "search", "search_range"]:
|
264
|
+
return mode
|
265
|
+
return "all"
|
266
|
+
|
267
|
+
|
251
268
|
def get_data_dir() -> str:
|
252
269
|
"""
|
253
270
|
获取Jarvis数据存储目录路径。
|
@@ -378,6 +395,20 @@ def get_roles_dirs() -> List[str]:
|
|
378
395
|
]
|
379
396
|
|
380
397
|
|
398
|
+
def get_after_tool_call_cb_dirs() -> List[str]:
|
399
|
+
"""
|
400
|
+
获取工具调用后回调函数实现目录。
|
401
|
+
|
402
|
+
返回:
|
403
|
+
List[str]: 工具调用后回调函数实现目录列表
|
404
|
+
"""
|
405
|
+
return [
|
406
|
+
os.path.expanduser(os.path.expandvars(str(p)))
|
407
|
+
for p in GLOBAL_CONFIG_DATA.get("JARVIS_AFTER_TOOL_CALL_CB_DIRS", [])
|
408
|
+
if p
|
409
|
+
]
|
410
|
+
|
411
|
+
|
381
412
|
def get_central_methodology_repo() -> str:
|
382
413
|
"""
|
383
414
|
获取中心方法论Git仓库地址。
|
jarvis/jarvis_utils/embedding.py
CHANGED
jarvis/jarvis_utils/git_utils.py
CHANGED
@@ -34,7 +34,13 @@ def find_git_root_and_cd(start_dir: str = ".") -> str:
|
|
34
34
|
"""
|
35
35
|
os.chdir(start_dir)
|
36
36
|
try:
|
37
|
-
|
37
|
+
result = subprocess.run(
|
38
|
+
["git", "rev-parse", "--show-toplevel"],
|
39
|
+
capture_output=True,
|
40
|
+
text=True,
|
41
|
+
check=True,
|
42
|
+
)
|
43
|
+
git_root = result.stdout.strip()
|
38
44
|
if not git_root:
|
39
45
|
subprocess.run(["git", "init"], check=True)
|
40
46
|
git_root = os.path.abspath(".")
|
@@ -291,7 +297,13 @@ def get_modified_line_ranges() -> Dict[str, List[Tuple[int, int]]]:
|
|
291
297
|
行号从1开始。
|
292
298
|
"""
|
293
299
|
# 获取所有文件的Git差异
|
294
|
-
|
300
|
+
# 仅用于解析修改行范围,减少上下文以降低输出体积和解析成本
|
301
|
+
result = subprocess.run(
|
302
|
+
["git", "show", "-U0", "--no-color"],
|
303
|
+
capture_output=True,
|
304
|
+
text=True,
|
305
|
+
)
|
306
|
+
diff_output = result.stdout
|
295
307
|
|
296
308
|
# 解析差异以获取修改的文件及其行范围
|
297
309
|
result: Dict[str, List[Tuple[int, int]]] = {}
|
@@ -417,25 +429,32 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
417
429
|
hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
|
418
430
|
)
|
419
431
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
432
|
+
# 检测 uv 可用性:优先虚拟环境内的 uv,其次 PATH 中的 uv
|
433
|
+
from shutil import which as _which
|
434
|
+
uv_executable = None
|
435
|
+
if sys.platform == "win32":
|
436
|
+
venv_uv = os.path.join(sys.prefix, "Scripts", "uv.exe")
|
437
|
+
else:
|
438
|
+
venv_uv = os.path.join(sys.prefix, "bin", "uv")
|
439
|
+
if os.path.exists(venv_uv):
|
440
|
+
uv_executable = venv_uv
|
441
|
+
else:
|
442
|
+
path_uv = _which("uv")
|
443
|
+
if path_uv:
|
444
|
+
uv_executable = path_uv
|
429
445
|
|
430
446
|
# 根据环境选择安装命令
|
431
447
|
# 检测是否安装了 RAG 特性(更精确)
|
432
448
|
rag_installed = is_rag_installed()
|
433
449
|
|
434
|
-
#
|
435
|
-
if
|
436
|
-
if
|
437
|
-
install_cmd = [
|
450
|
+
# 根据 uv 可用性与 RAG 特性选择安装命令(优先使用 uv)
|
451
|
+
if uv_executable:
|
452
|
+
if rag_installed:
|
453
|
+
install_cmd = [uv_executable, "pip", "install", "-e", ".[rag]"]
|
438
454
|
else:
|
455
|
+
install_cmd = [uv_executable, "pip", "install", "-e", "."]
|
456
|
+
else:
|
457
|
+
if rag_installed:
|
439
458
|
install_cmd = [
|
440
459
|
sys.executable,
|
441
460
|
"-m",
|
@@ -444,9 +463,6 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
444
463
|
"-e",
|
445
464
|
".[rag]",
|
446
465
|
]
|
447
|
-
else:
|
448
|
-
if is_uv_env:
|
449
|
-
install_cmd = ["uv", "pip", "install", "-e", "."]
|
450
466
|
else:
|
451
467
|
install_cmd = [
|
452
468
|
sys.executable,
|
jarvis/jarvis_utils/utils.py
CHANGED
@@ -252,17 +252,19 @@ def _check_pip_updates() -> bool:
|
|
252
252
|
hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
|
253
253
|
)
|
254
254
|
|
255
|
-
#
|
256
|
-
|
255
|
+
# 检测是否可用 uv(优先使用虚拟环境内的uv,其次PATH中的uv)
|
256
|
+
from shutil import which as _which
|
257
257
|
uv_executable: Optional[str] = None
|
258
|
-
if
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
258
|
+
if sys.platform == "win32":
|
259
|
+
venv_uv = Path(sys.prefix) / "Scripts" / "uv.exe"
|
260
|
+
else:
|
261
|
+
venv_uv = Path(sys.prefix) / "bin" / "uv"
|
262
|
+
if venv_uv.exists():
|
263
|
+
uv_executable = str(venv_uv)
|
264
|
+
else:
|
265
|
+
path_uv = _which("uv")
|
266
|
+
if path_uv:
|
267
|
+
uv_executable = path_uv
|
266
268
|
|
267
269
|
# 检测是否安装了 RAG 特性(更精确)
|
268
270
|
from jarvis.jarvis_utils.utils import (
|
@@ -274,7 +276,7 @@ def _check_pip_updates() -> bool:
|
|
274
276
|
package_spec = (
|
275
277
|
"jarvis-ai-assistant[rag]" if rag_installed else "jarvis-ai-assistant"
|
276
278
|
)
|
277
|
-
if
|
279
|
+
if uv_executable:
|
278
280
|
cmd_list = [uv_executable, "pip", "install", "--upgrade", package_spec]
|
279
281
|
update_cmd = f"uv pip install --upgrade {package_spec}"
|
280
282
|
else:
|
@@ -677,7 +679,7 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
677
679
|
|
678
680
|
# 愿景 Panel
|
679
681
|
vision_text = Text(
|
680
|
-
"
|
682
|
+
"让开发者与AI成为共生伙伴",
|
681
683
|
justify="center",
|
682
684
|
style="italic",
|
683
685
|
)
|
@@ -692,7 +694,7 @@ def _show_usage_stats(welcome_str: str) -> None:
|
|
692
694
|
|
693
695
|
# 使命 Panel
|
694
696
|
mission_text = Text(
|
695
|
-
"
|
697
|
+
"让灵感高效落地为代码与行动",
|
696
698
|
justify="center",
|
697
699
|
style="italic",
|
698
700
|
)
|
@@ -1140,11 +1142,17 @@ def _collect_optional_config_interactively(
|
|
1140
1142
|
)
|
1141
1143
|
|
1142
1144
|
# 新增的配置项交互(通用体验相关)
|
1145
|
+
# 根据平台统一默认值:Windows下为False,其它平台为True(与config.get_pretty_output一致)
|
1146
|
+
try:
|
1147
|
+
import platform as _platform_mod
|
1148
|
+
_default_pretty = False if _platform_mod.system() == "Windows" else True
|
1149
|
+
except Exception:
|
1150
|
+
_default_pretty = True
|
1143
1151
|
changed = (
|
1144
1152
|
_ask_and_set(
|
1145
1153
|
"JARVIS_PRETTY_OUTPUT",
|
1146
1154
|
"是否启用更美观的终端输出(Pretty Output)?",
|
1147
|
-
|
1155
|
+
_default_pretty,
|
1148
1156
|
"bool",
|
1149
1157
|
)
|
1150
1158
|
or changed
|
@@ -1198,7 +1206,7 @@ def _collect_optional_config_interactively(
|
|
1198
1206
|
_ask_and_set(
|
1199
1207
|
"JARVIS_FORCE_SAVE_MEMORY",
|
1200
1208
|
"是否强制保存会话记忆?",
|
1201
|
-
|
1209
|
+
False,
|
1202
1210
|
"bool",
|
1203
1211
|
)
|
1204
1212
|
or changed
|
@@ -1328,6 +1336,38 @@ def _collect_optional_config_interactively(
|
|
1328
1336
|
|
1329
1337
|
|
1330
1338
|
|
1339
|
+
new_mode = get_choice(
|
1340
|
+
tip,
|
1341
|
+
choices,
|
1342
|
+
)
|
1343
|
+
|
1344
|
+
if new_mode == current_mode:
|
1345
|
+
return False
|
1346
|
+
|
1347
|
+
config_data[_key] = new_mode
|
1348
|
+
return True
|
1349
|
+
except Exception:
|
1350
|
+
return False
|
1351
|
+
|
1352
|
+
def _ask_patch_format_mode() -> bool:
|
1353
|
+
try:
|
1354
|
+
_key = "JARVIS_PATCH_FORMAT"
|
1355
|
+
if not ask_all and _key in config_data:
|
1356
|
+
return False
|
1357
|
+
|
1358
|
+
from jarvis.jarvis_utils.input import get_choice
|
1359
|
+
from jarvis.jarvis_utils.config import get_patch_format
|
1360
|
+
|
1361
|
+
current_mode = config_data.get(_key, get_patch_format())
|
1362
|
+
choices = ["all", "search", "search_range"]
|
1363
|
+
tip = (
|
1364
|
+
"请选择补丁格式处理模式 (JARVIS_PATCH_FORMAT):\n"
|
1365
|
+
"该设置影响 edit_file_handler 在处理补丁时允许的匹配方式。\n"
|
1366
|
+
" - all: 同时支持 SEARCH 与 SEARCH_START/SEARCH_END 两种模式(默认)。\n"
|
1367
|
+
" - search: 仅允许精确片段匹配(SEARCH)。更稳定,适合较弱模型或严格控制改动。\n"
|
1368
|
+
" - search_range: 仅允许范围匹配(SEARCH_START/SEARCH_END)。更灵活,适合较强模型和块内细粒度修改。"
|
1369
|
+
)
|
1370
|
+
|
1331
1371
|
new_mode = get_choice(
|
1332
1372
|
tip,
|
1333
1373
|
choices,
|
@@ -1342,6 +1382,7 @@ def _collect_optional_config_interactively(
|
|
1342
1382
|
return False
|
1343
1383
|
|
1344
1384
|
changed = _ask_git_check_mode() or changed
|
1385
|
+
changed = _ask_patch_format_mode() or changed
|
1345
1386
|
|
1346
1387
|
# Git 提交提示词(可选)
|
1347
1388
|
changed = (
|
@@ -1728,9 +1769,22 @@ def get_file_md5(filepath: str) -> str:
|
|
1728
1769
|
filepath: 要计算哈希的文件路径
|
1729
1770
|
|
1730
1771
|
返回:
|
1731
|
-
str: 文件内容的MD5
|
1772
|
+
str: 文件内容的MD5哈希值(为降低内存占用,仅读取前100MB进行计算)
|
1732
1773
|
"""
|
1733
|
-
|
1774
|
+
# 采用流式读取,避免一次性加载100MB到内存
|
1775
|
+
h = hashlib.md5()
|
1776
|
+
max_bytes = 100 * 1024 * 1024 # 与原实现保持一致:仅读取前100MB
|
1777
|
+
buf_size = 8 * 1024 * 1024 # 8MB缓冲
|
1778
|
+
read_bytes = 0
|
1779
|
+
with open(filepath, "rb") as f:
|
1780
|
+
while read_bytes < max_bytes:
|
1781
|
+
to_read = min(buf_size, max_bytes - read_bytes)
|
1782
|
+
chunk = f.read(to_read)
|
1783
|
+
if not chunk:
|
1784
|
+
break
|
1785
|
+
h.update(chunk)
|
1786
|
+
read_bytes += len(chunk)
|
1787
|
+
return h.hexdigest()
|
1734
1788
|
|
1735
1789
|
|
1736
1790
|
def get_file_line_count(filename: str) -> int:
|
@@ -1743,7 +1797,9 @@ def get_file_line_count(filename: str) -> int:
|
|
1743
1797
|
int: 文件中的行数,如果文件无法读取则返回0
|
1744
1798
|
"""
|
1745
1799
|
try:
|
1746
|
-
|
1800
|
+
# 使用流式逐行计数,避免将整个文件读入内存
|
1801
|
+
with open(filename, "r", encoding="utf-8", errors="ignore") as f:
|
1802
|
+
return sum(1 for _ in f)
|
1747
1803
|
except Exception:
|
1748
1804
|
return 0
|
1749
1805
|
|