jarvis-ai-assistant 0.2.2__py3-none-any.whl → 0.2.4__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/edit_file_handler.py +5 -0
- jarvis/jarvis_agent/jarvis.py +22 -25
- jarvis/jarvis_agent/main.py +6 -6
- jarvis/jarvis_agent/prompts.py +26 -4
- jarvis/jarvis_code_agent/code_agent.py +279 -11
- jarvis/jarvis_code_analysis/code_review.py +21 -19
- jarvis/jarvis_data/config_schema.json +86 -18
- jarvis/jarvis_git_squash/main.py +3 -3
- jarvis/jarvis_git_utils/git_commiter.py +32 -11
- jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
- jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
- jarvis/jarvis_platform/tongyi.py +9 -9
- jarvis/jarvis_rag/cli.py +79 -23
- jarvis/jarvis_rag/query_rewriter.py +61 -12
- jarvis/jarvis_rag/rag_pipeline.py +143 -34
- jarvis/jarvis_rag/retriever.py +6 -6
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +337 -0
- jarvis/jarvis_stats/stats.py +433 -0
- jarvis/jarvis_stats/storage.py +329 -0
- jarvis/jarvis_stats/visualizer.py +443 -0
- jarvis/jarvis_tools/cli/main.py +84 -15
- jarvis/jarvis_tools/generate_new_tool.py +22 -1
- jarvis/jarvis_tools/registry.py +35 -16
- jarvis/jarvis_tools/search_web.py +3 -3
- jarvis/jarvis_tools/virtual_tty.py +315 -26
- jarvis/jarvis_utils/config.py +98 -11
- jarvis/jarvis_utils/git_utils.py +8 -16
- jarvis/jarvis_utils/globals.py +29 -8
- jarvis/jarvis_utils/input.py +114 -121
- jarvis/jarvis_utils/utils.py +213 -37
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/METADATA +99 -9
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/RECORD +39 -34
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,30 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
import fcntl
|
3
2
|
import os
|
4
|
-
import
|
5
|
-
import select
|
6
|
-
import signal
|
3
|
+
import sys
|
7
4
|
import time
|
8
|
-
from typing import Any, Dict
|
5
|
+
from typing import Any, Dict, TYPE_CHECKING
|
6
|
+
|
7
|
+
# 为了类型检查,总是导入这些模块
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
import fcntl
|
10
|
+
import pty
|
11
|
+
import select
|
12
|
+
import signal
|
13
|
+
import subprocess
|
14
|
+
import threading
|
15
|
+
import queue
|
16
|
+
|
17
|
+
# 平台相关的导入
|
18
|
+
if sys.platform != "win32":
|
19
|
+
import fcntl
|
20
|
+
import pty
|
21
|
+
import select
|
22
|
+
import signal
|
23
|
+
else:
|
24
|
+
# Windows平台的导入
|
25
|
+
import subprocess
|
26
|
+
import threading
|
27
|
+
import queue
|
9
28
|
|
10
29
|
|
11
30
|
class VirtualTTYTool:
|
@@ -14,6 +33,7 @@ class VirtualTTYTool:
|
|
14
33
|
"控制虚拟终端执行各种操作,如启动终端、输入命令、获取输出等。"
|
15
34
|
+ "与execute_script不同,此工具会创建一个持久的虚拟终端会话,可以连续执行多个命令,并保持终端状态。"
|
16
35
|
+ "适用于需要交互式操作的场景,如运行需要用户输入的交互式程序(如:ssh连接、sftp传输、gdb/dlv调试等)。"
|
36
|
+
+ "注意:Windows平台功能有限,某些Unix特有功能可能不可用。"
|
17
37
|
)
|
18
38
|
parameters = {
|
19
39
|
"type": "object",
|
@@ -76,11 +96,21 @@ class VirtualTTYTool:
|
|
76
96
|
|
77
97
|
# 如果指定的tty_id不存在,为其创建一个新的tty_data
|
78
98
|
if tty_id not in agent.tty_sessions:
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
99
|
+
if sys.platform == "win32":
|
100
|
+
import queue as _queue # pylint: disable=import-outside-toplevel
|
101
|
+
|
102
|
+
agent.tty_sessions[tty_id] = {
|
103
|
+
"process": None,
|
104
|
+
"output_queue": _queue.Queue(),
|
105
|
+
"output_thread": None,
|
106
|
+
"shell": "cmd.exe",
|
107
|
+
}
|
108
|
+
else:
|
109
|
+
agent.tty_sessions[tty_id] = {
|
110
|
+
"master_fd": None,
|
111
|
+
"pid": None,
|
112
|
+
"shell": "/bin/bash",
|
113
|
+
}
|
84
114
|
|
85
115
|
action = args.get("action", "").strip().lower()
|
86
116
|
|
@@ -164,13 +194,25 @@ class VirtualTTYTool:
|
|
164
194
|
|
165
195
|
def _launch_tty(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
166
196
|
"""启动虚拟终端"""
|
197
|
+
if sys.platform == "win32":
|
198
|
+
return self._launch_tty_windows(agent, tty_id)
|
199
|
+
else:
|
200
|
+
return self._launch_tty_unix(agent, tty_id)
|
201
|
+
|
202
|
+
def _launch_tty_unix(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
203
|
+
"""Unix/Linux平台启动虚拟终端"""
|
167
204
|
try:
|
168
205
|
# 如果该ID的终端已经启动,先关闭它
|
169
206
|
if agent.tty_sessions[tty_id]["master_fd"] is not None:
|
170
207
|
self._close_tty(agent, tty_id)
|
171
208
|
|
209
|
+
# 在Unix平台上导入需要的模块
|
210
|
+
import pty as _pty # pylint: disable=import-outside-toplevel
|
211
|
+
import fcntl as _fcntl # pylint: disable=import-outside-toplevel
|
212
|
+
import select as _select # pylint: disable=import-outside-toplevel
|
213
|
+
|
172
214
|
# 创建伪终端
|
173
|
-
pid, master_fd =
|
215
|
+
pid, master_fd = _pty.fork()
|
174
216
|
|
175
217
|
if pid == 0: # 子进程
|
176
218
|
# 执行shell
|
@@ -180,7 +222,7 @@ class VirtualTTYTool:
|
|
180
222
|
)
|
181
223
|
else: # 父进程
|
182
224
|
# 设置非阻塞模式
|
183
|
-
|
225
|
+
_fcntl.fcntl(master_fd, _fcntl.F_SETFL, os.O_NONBLOCK)
|
184
226
|
|
185
227
|
# 保存终端状态
|
186
228
|
agent.tty_sessions[tty_id]["master_fd"] = master_fd
|
@@ -191,7 +233,7 @@ class VirtualTTYTool:
|
|
191
233
|
start_time = time.time()
|
192
234
|
while time.time() - start_time < 2.0: # 最多等待2秒
|
193
235
|
try:
|
194
|
-
r, _, _ =
|
236
|
+
r, _, _ = _select.select([master_fd], [], [], 0.1)
|
195
237
|
if r:
|
196
238
|
data = os.read(master_fd, 1024)
|
197
239
|
if data:
|
@@ -211,6 +253,74 @@ class VirtualTTYTool:
|
|
211
253
|
"stderr": f"启动虚拟终端 [{tty_id}] 失败: {str(e)}",
|
212
254
|
}
|
213
255
|
|
256
|
+
def _launch_tty_windows(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
257
|
+
"""Windows平台启动虚拟终端"""
|
258
|
+
try:
|
259
|
+
# 如果该ID的终端已经启动,先关闭它
|
260
|
+
if agent.tty_sessions[tty_id]["process"] is not None:
|
261
|
+
self._close_tty(agent, tty_id)
|
262
|
+
|
263
|
+
# 在Windows平台上导入需要的模块
|
264
|
+
import subprocess as _subprocess # pylint: disable=import-outside-toplevel
|
265
|
+
import threading as _threading # pylint: disable=import-outside-toplevel
|
266
|
+
import queue as _queue # pylint: disable=import-outside-toplevel
|
267
|
+
|
268
|
+
# 创建子进程
|
269
|
+
process = _subprocess.Popen(
|
270
|
+
agent.tty_sessions[tty_id]["shell"],
|
271
|
+
stdin=_subprocess.PIPE,
|
272
|
+
stdout=_subprocess.PIPE,
|
273
|
+
stderr=_subprocess.STDOUT,
|
274
|
+
shell=True,
|
275
|
+
text=True,
|
276
|
+
bufsize=0,
|
277
|
+
encoding="utf-8",
|
278
|
+
errors="replace",
|
279
|
+
)
|
280
|
+
|
281
|
+
# 保存进程对象
|
282
|
+
agent.tty_sessions[tty_id]["process"] = process
|
283
|
+
|
284
|
+
# 创建输出读取线程
|
285
|
+
def read_output():
|
286
|
+
while True:
|
287
|
+
if process is None or process.poll() is not None:
|
288
|
+
break
|
289
|
+
try:
|
290
|
+
if process.stdout is None:
|
291
|
+
break
|
292
|
+
line = process.stdout.readline()
|
293
|
+
if line:
|
294
|
+
agent.tty_sessions[tty_id]["output_queue"].put(line)
|
295
|
+
except:
|
296
|
+
break
|
297
|
+
|
298
|
+
output_thread = _threading.Thread(target=read_output, daemon=True)
|
299
|
+
output_thread.start()
|
300
|
+
agent.tty_sessions[tty_id]["output_thread"] = output_thread
|
301
|
+
|
302
|
+
# 读取初始输出
|
303
|
+
output = ""
|
304
|
+
start_time = time.time()
|
305
|
+
while time.time() - start_time < 2.0: # 最多等待2秒
|
306
|
+
try:
|
307
|
+
line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
|
308
|
+
output += line
|
309
|
+
except _queue.Empty:
|
310
|
+
continue
|
311
|
+
|
312
|
+
if output:
|
313
|
+
print(f"📤 终端 [{tty_id}]: {output}")
|
314
|
+
|
315
|
+
return {"success": True, "stdout": output, "stderr": ""}
|
316
|
+
|
317
|
+
except Exception as e:
|
318
|
+
return {
|
319
|
+
"success": False,
|
320
|
+
"stdout": "",
|
321
|
+
"stderr": f"启动虚拟终端 [{tty_id}] 失败: {str(e)}",
|
322
|
+
}
|
323
|
+
|
214
324
|
def _input_command(
|
215
325
|
self,
|
216
326
|
agent: Any,
|
@@ -225,6 +335,22 @@ class VirtualTTYTool:
|
|
225
335
|
command: 要输入的单行命令
|
226
336
|
add_enter: 是否在命令末尾添加回车符
|
227
337
|
"""
|
338
|
+
if sys.platform == "win32":
|
339
|
+
return self._input_command_windows(
|
340
|
+
agent, tty_id, command, timeout, add_enter
|
341
|
+
)
|
342
|
+
else:
|
343
|
+
return self._input_command_unix(agent, tty_id, command, timeout, add_enter)
|
344
|
+
|
345
|
+
def _input_command_unix(
|
346
|
+
self,
|
347
|
+
agent: Any,
|
348
|
+
tty_id: str,
|
349
|
+
command: str,
|
350
|
+
timeout: float,
|
351
|
+
add_enter: bool = True,
|
352
|
+
) -> Dict[str, Any]:
|
353
|
+
"""Unix/Linux平台输入命令"""
|
228
354
|
if agent.tty_sessions[tty_id]["master_fd"] is None:
|
229
355
|
return {
|
230
356
|
"success": False,
|
@@ -251,7 +377,9 @@ class VirtualTTYTool:
|
|
251
377
|
while time.time() - start_time < timeout:
|
252
378
|
try:
|
253
379
|
# 使用select等待数据可读
|
254
|
-
|
380
|
+
import select as _select # pylint: disable=import-outside-toplevel
|
381
|
+
|
382
|
+
r, _, _ = _select.select(
|
255
383
|
[agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
|
256
384
|
)
|
257
385
|
if r:
|
@@ -270,10 +398,68 @@ class VirtualTTYTool:
|
|
270
398
|
"stderr": f"在终端 [{tty_id}] 执行命令失败: {str(e)}",
|
271
399
|
}
|
272
400
|
|
401
|
+
def _input_command_windows(
|
402
|
+
self,
|
403
|
+
agent: Any,
|
404
|
+
tty_id: str,
|
405
|
+
command: str,
|
406
|
+
timeout: float,
|
407
|
+
add_enter: bool = True,
|
408
|
+
) -> Dict[str, Any]:
|
409
|
+
"""Windows平台输入命令"""
|
410
|
+
if agent.tty_sessions[tty_id]["process"] is None:
|
411
|
+
return {
|
412
|
+
"success": False,
|
413
|
+
"stdout": "",
|
414
|
+
"stderr": f"虚拟终端 [{tty_id}] 未启动",
|
415
|
+
}
|
416
|
+
|
417
|
+
# 严格检查并拒绝多行输入
|
418
|
+
if "\n" in command:
|
419
|
+
return {"success": False, "stdout": "", "stderr": "错误:禁止多行输入"}
|
420
|
+
|
421
|
+
try:
|
422
|
+
# 根据add_enter参数决定是否添加回车符
|
423
|
+
if add_enter:
|
424
|
+
command = command + "\n"
|
425
|
+
|
426
|
+
# 发送命令
|
427
|
+
agent.tty_sessions[tty_id]["process"].stdin.write(command)
|
428
|
+
agent.tty_sessions[tty_id]["process"].stdin.flush()
|
429
|
+
|
430
|
+
# 等待输出
|
431
|
+
output = ""
|
432
|
+
start_time = time.time()
|
433
|
+
while time.time() - start_time < timeout:
|
434
|
+
try:
|
435
|
+
line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
|
436
|
+
output += line
|
437
|
+
except Exception: # queue.Empty
|
438
|
+
continue
|
439
|
+
|
440
|
+
print(f"📤 终端 [{tty_id}]: {output}")
|
441
|
+
return {"success": True, "stdout": output, "stderr": ""}
|
442
|
+
|
443
|
+
except Exception as e:
|
444
|
+
return {
|
445
|
+
"success": False,
|
446
|
+
"stdout": "",
|
447
|
+
"stderr": f"在终端 [{tty_id}] 执行命令失败: {str(e)}",
|
448
|
+
}
|
449
|
+
|
273
450
|
def _get_output(
|
274
451
|
self, agent: Any, tty_id: str, timeout: float = 5.0
|
275
452
|
) -> Dict[str, Any]:
|
276
453
|
"""获取终端输出"""
|
454
|
+
if sys.platform == "win32":
|
455
|
+
return self._get_output_windows(agent, tty_id, timeout)
|
456
|
+
else:
|
457
|
+
return self._get_output_unix(agent, tty_id, timeout)
|
458
|
+
|
459
|
+
def _get_output_unix(
|
460
|
+
self, agent: Any, tty_id: str, timeout: float = 5.0
|
461
|
+
) -> Dict[str, Any]:
|
462
|
+
"""Unix/Linux平台获取输出"""
|
277
463
|
if agent.tty_sessions[tty_id]["master_fd"] is None:
|
278
464
|
return {
|
279
465
|
"success": False,
|
@@ -287,7 +473,9 @@ class VirtualTTYTool:
|
|
287
473
|
|
288
474
|
while time.time() - start_time < timeout:
|
289
475
|
# 使用select等待数据可读
|
290
|
-
|
476
|
+
import select as _select # pylint: disable=import-outside-toplevel
|
477
|
+
|
478
|
+
r, _, _ = _select.select(
|
291
479
|
[agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
|
292
480
|
)
|
293
481
|
if r:
|
@@ -313,8 +501,47 @@ class VirtualTTYTool:
|
|
313
501
|
"stderr": f"获取终端 [{tty_id}] 输出失败: {str(e)}",
|
314
502
|
}
|
315
503
|
|
504
|
+
def _get_output_windows(
|
505
|
+
self, agent: Any, tty_id: str, timeout: float = 5.0
|
506
|
+
) -> Dict[str, Any]:
|
507
|
+
"""Windows平台获取输出"""
|
508
|
+
if agent.tty_sessions[tty_id]["process"] is None:
|
509
|
+
return {
|
510
|
+
"success": False,
|
511
|
+
"stdout": "",
|
512
|
+
"stderr": f"虚拟终端 [{tty_id}] 未启动",
|
513
|
+
}
|
514
|
+
|
515
|
+
try:
|
516
|
+
output = ""
|
517
|
+
start_time = time.time()
|
518
|
+
|
519
|
+
while time.time() - start_time < timeout:
|
520
|
+
try:
|
521
|
+
line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
|
522
|
+
output += line
|
523
|
+
except Exception: # queue.Empty
|
524
|
+
continue
|
525
|
+
|
526
|
+
print(f"📤 终端 [{tty_id}]: {output}")
|
527
|
+
return {"success": True, "stdout": output, "stderr": ""}
|
528
|
+
|
529
|
+
except Exception as e:
|
530
|
+
return {
|
531
|
+
"success": False,
|
532
|
+
"stdout": "",
|
533
|
+
"stderr": f"获取终端 [{tty_id}] 输出失败: {str(e)}",
|
534
|
+
}
|
535
|
+
|
316
536
|
def _close_tty(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
317
537
|
"""关闭虚拟终端"""
|
538
|
+
if sys.platform == "win32":
|
539
|
+
return self._close_tty_windows(agent, tty_id)
|
540
|
+
else:
|
541
|
+
return self._close_tty_unix(agent, tty_id)
|
542
|
+
|
543
|
+
def _close_tty_unix(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
544
|
+
"""Unix/Linux平台关闭终端"""
|
318
545
|
if agent.tty_sessions[tty_id]["master_fd"] is None:
|
319
546
|
return {
|
320
547
|
"success": True,
|
@@ -328,7 +555,9 @@ class VirtualTTYTool:
|
|
328
555
|
|
329
556
|
# 终止子进程
|
330
557
|
if agent.tty_sessions[tty_id]["pid"]:
|
331
|
-
|
558
|
+
import signal as _signal # pylint: disable=import-outside-toplevel
|
559
|
+
|
560
|
+
os.kill(agent.tty_sessions[tty_id]["pid"], _signal.SIGTERM)
|
332
561
|
|
333
562
|
# 重置终端数据
|
334
563
|
agent.tty_sessions[tty_id] = {
|
@@ -350,8 +579,53 @@ class VirtualTTYTool:
|
|
350
579
|
"stderr": f"关闭虚拟终端 [{tty_id}] 失败: {str(e)}",
|
351
580
|
}
|
352
581
|
|
582
|
+
def _close_tty_windows(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
583
|
+
"""Windows平台关闭终端"""
|
584
|
+
if agent.tty_sessions[tty_id]["process"] is None:
|
585
|
+
return {
|
586
|
+
"success": True,
|
587
|
+
"stdout": f"没有正在运行的虚拟终端 [{tty_id}]",
|
588
|
+
"stderr": "",
|
589
|
+
}
|
590
|
+
|
591
|
+
try:
|
592
|
+
# 终止进程
|
593
|
+
agent.tty_sessions[tty_id]["process"].terminate()
|
594
|
+
agent.tty_sessions[tty_id]["process"].wait()
|
595
|
+
|
596
|
+
# 重置终端数据
|
597
|
+
import queue as _queue # pylint: disable=import-outside-toplevel
|
598
|
+
|
599
|
+
agent.tty_sessions[tty_id] = {
|
600
|
+
"process": None,
|
601
|
+
"output_queue": _queue.Queue(),
|
602
|
+
"output_thread": None,
|
603
|
+
"shell": "cmd.exe",
|
604
|
+
}
|
605
|
+
|
606
|
+
return {
|
607
|
+
"success": True,
|
608
|
+
"stdout": f"虚拟终端 [{tty_id}] 已关闭",
|
609
|
+
"stderr": "",
|
610
|
+
}
|
611
|
+
|
612
|
+
except Exception as e:
|
613
|
+
return {
|
614
|
+
"success": False,
|
615
|
+
"stdout": "",
|
616
|
+
"stderr": f"关闭虚拟终端 [{tty_id}] 失败: {str(e)}",
|
617
|
+
}
|
618
|
+
|
353
619
|
def _get_screen(self, agent: Any, tty_id: str) -> Dict[str, Any]:
|
354
620
|
"""获取当前终端屏幕内容"""
|
621
|
+
if sys.platform == "win32":
|
622
|
+
# Windows平台暂不支持获取屏幕内容
|
623
|
+
return {
|
624
|
+
"success": False,
|
625
|
+
"stdout": "",
|
626
|
+
"stderr": "Windows平台暂不支持获取屏幕内容功能",
|
627
|
+
}
|
628
|
+
|
355
629
|
if agent.tty_sessions[tty_id]["master_fd"] is None:
|
356
630
|
return {
|
357
631
|
"success": False,
|
@@ -371,7 +645,9 @@ class VirtualTTYTool:
|
|
371
645
|
start_time = time.time()
|
372
646
|
while time.time() - start_time < 2.0: # 最多等待2秒
|
373
647
|
try:
|
374
|
-
|
648
|
+
import select as _select # pylint: disable=import-outside-toplevel
|
649
|
+
|
650
|
+
r, _, _ = _select.select(
|
375
651
|
[agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
|
376
652
|
)
|
377
653
|
if r:
|
@@ -404,15 +680,28 @@ class VirtualTTYTool:
|
|
404
680
|
active_ttys = []
|
405
681
|
|
406
682
|
for tty_id, tty_data in agent.tty_sessions.items():
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
683
|
+
if sys.platform == "win32":
|
684
|
+
status = "活动" if tty_data["process"] is not None else "关闭"
|
685
|
+
active_ttys.append(
|
686
|
+
{
|
687
|
+
"id": tty_id,
|
688
|
+
"status": status,
|
689
|
+
"pid": tty_data["process"].pid
|
690
|
+
if tty_data["process"]
|
691
|
+
else None,
|
692
|
+
"shell": tty_data["shell"],
|
693
|
+
}
|
694
|
+
)
|
695
|
+
else:
|
696
|
+
status = "活动" if tty_data["master_fd"] is not None else "关闭"
|
697
|
+
active_ttys.append(
|
698
|
+
{
|
699
|
+
"id": tty_id,
|
700
|
+
"status": status,
|
701
|
+
"pid": tty_data["pid"] if tty_data["pid"] else None,
|
702
|
+
"shell": tty_data["shell"],
|
703
|
+
}
|
704
|
+
)
|
416
705
|
|
417
706
|
# 格式化输出
|
418
707
|
output = "虚拟终端列表:\n"
|
jarvis/jarvis_utils/config.py
CHANGED
@@ -115,22 +115,26 @@ def get_shell_name() -> str:
|
|
115
115
|
return os.path.basename(shell_path).lower()
|
116
116
|
|
117
117
|
|
118
|
-
def _get_resolved_model_config(
|
118
|
+
def _get_resolved_model_config(
|
119
|
+
model_group_override: Optional[str] = None,
|
120
|
+
) -> Dict[str, Any]:
|
119
121
|
"""
|
120
122
|
解析并合并模型配置,处理模型组。
|
121
123
|
|
122
124
|
优先级顺序:
|
123
125
|
1. 单独的环境变量 (JARVIS_PLATFORM, JARVIS_MODEL, etc.)
|
124
|
-
2.
|
126
|
+
2. JARVIS_LLM_GROUP 中定义的组配置
|
125
127
|
3. 代码中的默认值
|
126
128
|
|
127
129
|
返回:
|
128
130
|
Dict[str, Any]: 解析后的模型配置字典
|
129
131
|
"""
|
130
132
|
group_config = {}
|
131
|
-
model_group_name = model_group_override or GLOBAL_CONFIG_DATA.get(
|
133
|
+
model_group_name = model_group_override or GLOBAL_CONFIG_DATA.get(
|
134
|
+
"JARVIS_LLM_GROUP"
|
135
|
+
)
|
132
136
|
# The format is a list of single-key dicts: [{'group_name': {...}}, ...]
|
133
|
-
model_groups = GLOBAL_CONFIG_DATA.get("
|
137
|
+
model_groups = GLOBAL_CONFIG_DATA.get("JARVIS_LLM_GROUPS", [])
|
134
138
|
|
135
139
|
if model_group_name and isinstance(model_groups, list):
|
136
140
|
for group_item in model_groups:
|
@@ -202,7 +206,9 @@ def get_thinking_model_name(model_group_override: Optional[str] = None) -> str:
|
|
202
206
|
"""
|
203
207
|
config = _get_resolved_model_config(model_group_override)
|
204
208
|
# Fallback to normal model if thinking model is not specified
|
205
|
-
return config.get(
|
209
|
+
return config.get(
|
210
|
+
"JARVIS_THINKING_MODEL", get_normal_model_name(model_group_override)
|
211
|
+
)
|
206
212
|
|
207
213
|
|
208
214
|
def is_execute_tool_confirm() -> bool:
|
@@ -256,6 +262,12 @@ def get_pretty_output() -> bool:
|
|
256
262
|
返回:
|
257
263
|
bool: 如果启用PrettyOutput则返回True,默认为True
|
258
264
|
"""
|
265
|
+
import platform
|
266
|
+
|
267
|
+
# Windows系统强制设置为False
|
268
|
+
if platform.system() == "Windows":
|
269
|
+
return False
|
270
|
+
|
259
271
|
return GLOBAL_CONFIG_DATA.get("JARVIS_PRETTY_OUTPUT", False) == True
|
260
272
|
|
261
273
|
|
@@ -334,14 +346,65 @@ def get_mcp_config() -> List[Dict[str, Any]]:
|
|
334
346
|
# ==============================================================================
|
335
347
|
|
336
348
|
|
337
|
-
|
349
|
+
DEFAULT_RAG_GROUPS = [
|
350
|
+
{
|
351
|
+
"text": {
|
352
|
+
"embedding_model": "BAAI/bge-m3",
|
353
|
+
"rerank_model": "BAAI/bge-reranker-v2-m3",
|
354
|
+
"use_bm25": True,
|
355
|
+
"use_rerank": True,
|
356
|
+
}
|
357
|
+
},
|
358
|
+
{
|
359
|
+
"code": {
|
360
|
+
"embedding_model": "Qodo/Qodo-Embed-1-7B",
|
361
|
+
"use_bm25": False,
|
362
|
+
"use_rerank": False,
|
363
|
+
}
|
364
|
+
},
|
365
|
+
]
|
366
|
+
|
367
|
+
|
368
|
+
def _get_resolved_rag_config(
|
369
|
+
rag_group_override: Optional[str] = None,
|
370
|
+
) -> Dict[str, Any]:
|
338
371
|
"""
|
339
|
-
|
372
|
+
解析并合并RAG配置,处理RAG组。
|
373
|
+
|
374
|
+
优先级顺序:
|
375
|
+
1. JARVIS_RAG 中的顶级设置 (embedding_model, etc.)
|
376
|
+
2. JARVIS_RAG_GROUP 中定义的组配置
|
377
|
+
3. 代码中的默认值
|
340
378
|
|
341
379
|
返回:
|
342
|
-
Dict[str, Any]: RAG配置字典
|
380
|
+
Dict[str, Any]: 解析后的RAG配置字典
|
343
381
|
"""
|
344
|
-
|
382
|
+
group_config = {}
|
383
|
+
rag_group_name = rag_group_override or GLOBAL_CONFIG_DATA.get("JARVIS_RAG_GROUP")
|
384
|
+
rag_groups = GLOBAL_CONFIG_DATA.get("JARVIS_RAG_GROUPS", DEFAULT_RAG_GROUPS)
|
385
|
+
|
386
|
+
if rag_group_name and isinstance(rag_groups, list):
|
387
|
+
for group_item in rag_groups:
|
388
|
+
if isinstance(group_item, dict) and rag_group_name in group_item:
|
389
|
+
group_config = group_item[rag_group_name]
|
390
|
+
break
|
391
|
+
|
392
|
+
# Start with group config
|
393
|
+
resolved_config = group_config.copy()
|
394
|
+
|
395
|
+
# Override with specific settings from the top-level JARVIS_RAG dict
|
396
|
+
top_level_rag_config = GLOBAL_CONFIG_DATA.get("JARVIS_RAG", {})
|
397
|
+
if isinstance(top_level_rag_config, dict):
|
398
|
+
for key in [
|
399
|
+
"embedding_model",
|
400
|
+
"rerank_model",
|
401
|
+
"use_bm25",
|
402
|
+
"use_rerank",
|
403
|
+
]:
|
404
|
+
if key in top_level_rag_config:
|
405
|
+
resolved_config[key] = top_level_rag_config[key]
|
406
|
+
|
407
|
+
return resolved_config
|
345
408
|
|
346
409
|
|
347
410
|
def get_rag_embedding_model() -> str:
|
@@ -351,7 +414,8 @@ def get_rag_embedding_model() -> str:
|
|
351
414
|
返回:
|
352
415
|
str: 嵌入模型的名称
|
353
416
|
"""
|
354
|
-
|
417
|
+
config = _get_resolved_rag_config()
|
418
|
+
return config.get("embedding_model", "BAAI/bge-m3")
|
355
419
|
|
356
420
|
|
357
421
|
def get_rag_rerank_model() -> str:
|
@@ -361,7 +425,8 @@ def get_rag_rerank_model() -> str:
|
|
361
425
|
返回:
|
362
426
|
str: rerank模型的名称
|
363
427
|
"""
|
364
|
-
|
428
|
+
config = _get_resolved_rag_config()
|
429
|
+
return config.get("rerank_model", "BAAI/bge-reranker-v2-m3")
|
365
430
|
|
366
431
|
|
367
432
|
def get_rag_embedding_cache_path() -> str:
|
@@ -382,3 +447,25 @@ def get_rag_vector_db_path() -> str:
|
|
382
447
|
str: 数据库路径
|
383
448
|
"""
|
384
449
|
return ".jarvis/rag/vectordb"
|
450
|
+
|
451
|
+
|
452
|
+
def get_rag_use_bm25() -> bool:
|
453
|
+
"""
|
454
|
+
获取RAG是否使用BM25。
|
455
|
+
|
456
|
+
返回:
|
457
|
+
bool: 如果使用BM25则返回True,默认为True
|
458
|
+
"""
|
459
|
+
config = _get_resolved_rag_config()
|
460
|
+
return config.get("use_bm25", True) is True
|
461
|
+
|
462
|
+
|
463
|
+
def get_rag_use_rerank() -> bool:
|
464
|
+
"""
|
465
|
+
获取RAG是否使用rerank。
|
466
|
+
|
467
|
+
返回:
|
468
|
+
bool: 如果使用rerank则返回True,默认为True
|
469
|
+
"""
|
470
|
+
config = _get_resolved_rag_config()
|
471
|
+
return config.get("use_rerank", True) is True
|
jarvis/jarvis_utils/git_utils.py
CHANGED
@@ -214,9 +214,7 @@ def handle_commit_workflow() -> bool:
|
|
214
214
|
Returns:
|
215
215
|
bool: 提交是否成功
|
216
216
|
"""
|
217
|
-
if is_confirm_before_apply_patch() and not user_confirm(
|
218
|
-
"是否要提交代码?", default=True
|
219
|
-
):
|
217
|
+
if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
|
220
218
|
revert_change()
|
221
219
|
return False
|
222
220
|
|
@@ -429,9 +427,7 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
429
427
|
if not in_venv and (
|
430
428
|
"Permission denied" in error_msg or "not writeable" in error_msg
|
431
429
|
):
|
432
|
-
if user_confirm(
|
433
|
-
"检测到权限问题,是否尝试用户级安装(--user)?", True
|
434
|
-
):
|
430
|
+
if user_confirm("检测到权限问题,是否尝试用户级安装(--user)?", True):
|
435
431
|
user_result = subprocess.run(
|
436
432
|
install_cmd + ["--user"],
|
437
433
|
cwd=git_root,
|
@@ -446,9 +442,7 @@ def check_and_update_git_repo(repo_path: str) -> bool:
|
|
446
442
|
PrettyOutput.print(f"代码安装失败: {error_msg}", OutputType.ERROR)
|
447
443
|
return False
|
448
444
|
except Exception as e:
|
449
|
-
PrettyOutput.print(
|
450
|
-
f"安装过程中发生意外错误: {str(e)}", OutputType.ERROR
|
451
|
-
)
|
445
|
+
PrettyOutput.print(f"安装过程中发生意外错误: {str(e)}", OutputType.ERROR)
|
452
446
|
return False
|
453
447
|
# 更新检查日期文件
|
454
448
|
with open(last_check_file, "w") as f:
|
@@ -482,9 +476,7 @@ def get_diff_file_list() -> List[str]:
|
|
482
476
|
subprocess.run(["git", "reset"], check=True)
|
483
477
|
|
484
478
|
if result.returncode != 0:
|
485
|
-
PrettyOutput.print(
|
486
|
-
f"获取差异文件列表失败: {result.stderr}", OutputType.ERROR
|
487
|
-
)
|
479
|
+
PrettyOutput.print(f"获取差异文件列表失败: {result.stderr}", OutputType.ERROR)
|
488
480
|
return []
|
489
481
|
|
490
482
|
return [f for f in result.stdout.splitlines() if f]
|
@@ -533,8 +525,10 @@ def get_recent_commits_with_files() -> List[Dict[str, Any]]:
|
|
533
525
|
],
|
534
526
|
capture_output=True,
|
535
527
|
text=True,
|
528
|
+
encoding="utf-8",
|
529
|
+
errors="replace",
|
536
530
|
)
|
537
|
-
if result.returncode != 0:
|
531
|
+
if result.returncode != 0 or result.stdout is None:
|
538
532
|
return []
|
539
533
|
|
540
534
|
# 解析提交信息
|
@@ -632,9 +626,7 @@ def confirm_add_new_files() -> None:
|
|
632
626
|
need_confirm = True
|
633
627
|
|
634
628
|
if binary_files:
|
635
|
-
output_lines.append(
|
636
|
-
f"检测到{len(binary_files)}个二进制文件(选择N将重新检测)"
|
637
|
-
)
|
629
|
+
output_lines.append(f"检测到{len(binary_files)}个二进制文件(选择N将重新检测)")
|
638
630
|
output_lines.append("二进制文件列表:")
|
639
631
|
output_lines.extend(f" - {file}" for file in binary_files)
|
640
632
|
need_confirm = True
|