jarvis-ai-assistant 0.3.28__py3-none-any.whl → 0.3.30__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.
Files changed (40) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +154 -32
  3. jarvis/jarvis_agent/event_bus.py +2 -2
  4. jarvis/jarvis_agent/events.py +157 -0
  5. jarvis/jarvis_agent/file_methodology_manager.py +17 -4
  6. jarvis/jarvis_agent/jarvis.py +3 -3
  7. jarvis/jarvis_agent/memory_manager.py +4 -3
  8. jarvis/jarvis_agent/prompts.py +2 -2
  9. jarvis/jarvis_agent/protocols.py +4 -1
  10. jarvis/jarvis_agent/run_loop.py +10 -27
  11. jarvis/jarvis_agent/shell_input_handler.py +6 -1
  12. jarvis/jarvis_agent/task_analyzer.py +18 -13
  13. jarvis/jarvis_agent/task_manager.py +6 -4
  14. jarvis/jarvis_agent/utils.py +50 -0
  15. jarvis/jarvis_code_agent/code_agent.py +3 -2
  16. jarvis/jarvis_data/config_schema.json +8 -0
  17. jarvis/jarvis_mcp/sse_mcp_client.py +1 -1
  18. jarvis/jarvis_memory_organizer/memory_organizer.py +4 -4
  19. jarvis/jarvis_platform/kimi.py +1 -1
  20. jarvis/jarvis_platform/tongyi.py +1 -1
  21. jarvis/jarvis_platform/yuanbao.py +1 -1
  22. jarvis/jarvis_rag/retriever.py +3 -3
  23. jarvis/jarvis_stats/stats.py +2 -2
  24. jarvis/jarvis_stats/storage.py +3 -3
  25. jarvis/jarvis_tools/edit_file.py +3 -3
  26. jarvis/jarvis_tools/execute_script.py +2 -2
  27. jarvis/jarvis_tools/generate_new_tool.py +13 -2
  28. jarvis/jarvis_tools/sub_agent.py +2 -2
  29. jarvis/jarvis_tools/sub_code_agent.py +2 -2
  30. jarvis/jarvis_utils/config.py +17 -3
  31. jarvis/jarvis_utils/fzf.py +4 -3
  32. jarvis/jarvis_utils/git_utils.py +20 -16
  33. jarvis/jarvis_utils/input.py +5 -5
  34. jarvis/jarvis_utils/utils.py +109 -20
  35. {jarvis_ai_assistant-0.3.28.dist-info → jarvis_ai_assistant-0.3.30.dist-info}/METADATA +1 -1
  36. {jarvis_ai_assistant-0.3.28.dist-info → jarvis_ai_assistant-0.3.30.dist-info}/RECORD +40 -38
  37. {jarvis_ai_assistant-0.3.28.dist-info → jarvis_ai_assistant-0.3.30.dist-info}/WHEEL +0 -0
  38. {jarvis_ai_assistant-0.3.28.dist-info → jarvis_ai_assistant-0.3.30.dist-info}/entry_points.txt +0 -0
  39. {jarvis_ai_assistant-0.3.28.dist-info → jarvis_ai_assistant-0.3.30.dist-info}/licenses/LICENSE +0 -0
  40. {jarvis_ai_assistant-0.3.28.dist-info → jarvis_ai_assistant-0.3.30.dist-info}/top_level.txt +0 -0
@@ -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"]
@@ -11,7 +11,8 @@ from enum import Enum
11
11
  from typing import Any, TYPE_CHECKING
12
12
 
13
13
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
14
- from jarvis.jarvis_utils.tag import ot
14
+ from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL
15
+ from jarvis.jarvis_agent.utils import join_prompts, is_auto_complete, normalize_next_action
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  # 仅用于类型标注,避免运行时循环依赖
@@ -63,7 +64,7 @@ class AgentRunLoop:
63
64
  # 广播工具调用前事件(不影响主流程)
64
65
  try:
65
66
  ag.event_bus.emit(
66
- "before_tool_call",
67
+ BEFORE_TOOL_CALL,
67
68
  agent=ag,
68
69
  current_response=current_response,
69
70
  )
@@ -72,23 +73,16 @@ class AgentRunLoop:
72
73
  need_return, tool_prompt = ag._call_tools(current_response)
73
74
 
74
75
  # 将上一个提示和工具提示安全地拼接起来
75
- prompt_parts = []
76
- if ag.session.prompt:
77
- prompt_parts.append(ag.session.prompt)
78
- if tool_prompt:
79
- prompt_parts.append(tool_prompt)
80
- ag.session.prompt = "\n\n".join(prompt_parts)
76
+ ag.session.prompt = join_prompts([ag.session.prompt, tool_prompt])
81
77
 
82
78
  if need_return:
83
79
  return ag.session.prompt
84
80
 
85
- # 执行回调
86
- if ag.after_tool_call_cb:
87
- ag.after_tool_call_cb(ag)
81
+
88
82
  # 广播工具调用后的事件(不影响主流程)
89
83
  try:
90
84
  ag.event_bus.emit(
91
- "after_tool_call",
85
+ AFTER_TOOL_CALL,
92
86
  agent=ag,
93
87
  current_response=current_response,
94
88
  need_return=need_return,
@@ -102,27 +96,16 @@ class AgentRunLoop:
102
96
  continue
103
97
 
104
98
  # 检查自动完成
105
- if ag.auto_complete and ot("!!!COMPLETE!!!") in current_response:
99
+ if ag.auto_complete and is_auto_complete(current_response):
106
100
  return ag._complete_task(auto_completed=True)
107
101
 
108
102
  # 获取下一步用户输入
109
103
  next_action = ag._get_next_user_action()
110
- if (
111
- next_action == "continue"
112
- or (
113
- isinstance(next_action, Enum)
114
- and getattr(next_action, "value", None) == "continue"
115
- )
116
- ):
104
+ action = normalize_next_action(next_action)
105
+ if action == "continue":
117
106
  run_input_handlers = True
118
107
  continue
119
- elif (
120
- next_action == "complete"
121
- or (
122
- isinstance(next_action, Enum)
123
- and getattr(next_action, "value", None) == "complete"
124
- )
125
- ):
108
+ elif action == "complete":
126
109
  return ag._complete_task(auto_completed=False)
127
110
 
128
111
  except Exception as e:
@@ -3,6 +3,7 @@ from typing import Any, Tuple
3
3
 
4
4
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
5
5
  from jarvis.jarvis_utils.input import user_confirm
6
+ from jarvis.jarvis_agent.utils import join_prompts
6
7
 
7
8
 
8
9
  def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
@@ -41,7 +42,11 @@ def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
41
42
  )
42
43
  if user_confirm("是否将执行结果反馈给Agent?", default=True):
43
44
  return (
44
- f"{user_input}\n\n用户执行以下脚本:\n{script}\n\n执行结果:\n{output}",
45
+ join_prompts([
46
+ user_input,
47
+ f"用户执行以下脚本:\n{script}",
48
+ f"执行结果:\n{output}",
49
+ ]),
45
50
  False,
46
51
  )
47
52
  return "", True
@@ -8,6 +8,8 @@ from jarvis.jarvis_utils.globals import get_interrupt, set_interrupt
8
8
 
9
9
  from jarvis.jarvis_agent.prompts import TASK_ANALYSIS_PROMPT
10
10
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
11
+ from jarvis.jarvis_agent.utils import join_prompts
12
+ from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL, BEFORE_SUMMARY, TASK_COMPLETED
11
13
 
12
14
 
13
15
  class TaskAnalyzer:
@@ -55,10 +57,7 @@ class TaskAnalyzer:
55
57
 
56
58
  def _prepare_analysis_prompt(self, satisfaction_feedback: str) -> str:
57
59
  """准备分析提示"""
58
- analysis_prompt = TASK_ANALYSIS_PROMPT
59
- if satisfaction_feedback:
60
- analysis_prompt += satisfaction_feedback
61
- return analysis_prompt
60
+ return join_prompts([TASK_ANALYSIS_PROMPT, satisfaction_feedback])
62
61
 
63
62
  def _process_analysis_loop(self):
64
63
  """处理分析循环"""
@@ -74,7 +73,7 @@ class TaskAnalyzer:
74
73
  # 执行工具调用(补充事件:before_tool_call/after_tool_call)
75
74
  try:
76
75
  self.agent.event_bus.emit(
77
- "before_tool_call",
76
+ BEFORE_TOOL_CALL,
78
77
  agent=self.agent,
79
78
  current_response=response,
80
79
  )
@@ -84,7 +83,7 @@ class TaskAnalyzer:
84
83
  self.agent.session.prompt = tool_prompt
85
84
  try:
86
85
  self.agent.event_bus.emit(
87
- "after_tool_call",
86
+ AFTER_TOOL_CALL,
88
87
  agent=self.agent,
89
88
  current_response=response,
90
89
  need_return=need_return,
@@ -130,9 +129,15 @@ class TaskAnalyzer:
130
129
  def _handle_interrupt_with_tool_calls(self, user_input: str) -> str:
131
130
  """处理有工具调用时的中断"""
132
131
  if self.agent.user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
133
- return f"被用户中断,用户补充信息为:{user_input}\n\n用户同意继续工具调用。"
132
+ return join_prompts([
133
+ f"被用户中断,用户补充信息为:{user_input}",
134
+ "用户同意继续工具调用。"
135
+ ])
134
136
  else:
135
- return f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
137
+ return join_prompts([
138
+ f"被用户中断,用户补充信息为:{user_input}",
139
+ "检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
140
+ ])
136
141
 
137
142
  def collect_satisfaction_feedback(self, auto_completed: bool) -> str:
138
143
  """收集满意度反馈"""
@@ -140,18 +145,18 @@ class TaskAnalyzer:
140
145
 
141
146
  if not auto_completed and self.agent.use_analysis:
142
147
  if self.agent.user_confirm("您对本次任务的完成是否满意?", True):
143
- satisfaction_feedback = "\n\n用户对本次任务的完成表示满意。"
148
+ satisfaction_feedback = "用户对本次任务的完成表示满意。"
144
149
  else:
145
150
  feedback = self.agent._multiline_input(
146
151
  "请提供您的反馈意见(可留空直接回车):", False
147
152
  )
148
153
  if feedback:
149
154
  satisfaction_feedback = (
150
- f"\n\n用户对本次任务的完成不满意,反馈意见如下:\n{feedback}"
155
+ f"用户对本次任务的完成不满意,反馈意见如下:\n{feedback}"
151
156
  )
152
157
  else:
153
158
  satisfaction_feedback = (
154
- "\n\n用户对本次任务的完成不满意,未提供具体反馈意见。"
159
+ "用户对本次任务的完成不满意,未提供具体反馈意见。"
155
160
  )
156
161
 
157
162
  return satisfaction_feedback
@@ -162,9 +167,9 @@ class TaskAnalyzer:
162
167
  def _subscribe_events(self) -> None:
163
168
  bus = self.agent.get_event_bus() # type: ignore[attr-defined]
164
169
  # 在生成总结前触发(保持与原顺序一致)
165
- bus.subscribe("before_summary", self._on_before_summary)
170
+ bus.subscribe(BEFORE_SUMMARY, self._on_before_summary)
166
171
  # 当无需总结时,作为兜底触发分析
167
- bus.subscribe("task_completed", self._on_task_completed)
172
+ bus.subscribe(TASK_COMPLETED, self._on_task_completed)
168
173
 
169
174
  def _on_before_summary(self, **payload) -> None:
170
175
  if self._analysis_done:
@@ -14,6 +14,7 @@ from jarvis.jarvis_agent import (
14
14
  get_multiline_input,
15
15
  user_confirm,
16
16
  )
17
+ from jarvis.jarvis_agent.utils import join_prompts
17
18
  from jarvis.jarvis_utils.config import get_data_dir
18
19
  from jarvis.jarvis_utils.fzf import fzf_select
19
20
 
@@ -109,7 +110,7 @@ class TaskManager:
109
110
  if need_additional:
110
111
  additional_input = get_multiline_input("请输入补充信息:")
111
112
  if additional_input:
112
- selected_task = f"{selected_task}\n\n补充信息:\n{additional_input}"
113
+ selected_task = join_prompts([selected_task, f"补充信息:\n{additional_input}"])
113
114
  return selected_task
114
115
  except Exception:
115
116
  # 如果解析失败,则回退到手动输入
@@ -138,9 +139,10 @@ class TaskManager:
138
139
  if need_additional:
139
140
  additional_input = get_multiline_input("请输入补充信息:")
140
141
  if additional_input:
141
- selected_task = (
142
- f"{selected_task}\n\n补充信息:\n{additional_input}"
143
- )
142
+ selected_task = join_prompts([
143
+ selected_task,
144
+ f"补充信息:\n{additional_input}"
145
+ ])
144
146
  return selected_task
145
147
  PrettyOutput.print(
146
148
  "无效的选择。请选择列表中的一个号码。", OutputType.WARNING
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 工具函数(jarvis_agent.utils)
4
+
5
+ - join_prompts: 统一的提示拼接策略(仅拼接非空段落,使用双换行)
6
+ - is_auto_complete: 统一的自动完成标记检测
7
+ """
8
+ from typing import Iterable, List, Any
9
+ from enum import Enum
10
+ from jarvis.jarvis_utils.tag import ot
11
+
12
+ def join_prompts(parts: Iterable[str]) -> str:
13
+ """
14
+ 将多个提示片段按统一规则拼接:
15
+ - 过滤掉空字符串
16
+ - 使用两个换行分隔
17
+ - 不进行额外 strip,保持调用方原样语义
18
+ """
19
+ non_empty: List[str] = [p for p in parts if p]
20
+ return "\n\n".join(non_empty)
21
+
22
+ def is_auto_complete(response: str) -> bool:
23
+ """
24
+ 检测是否包含自动完成标记。
25
+ 当前实现:包含 ot('!!!COMPLETE!!!') 即视为自动完成。
26
+ """
27
+ try:
28
+ return ot("!!!COMPLETE!!!") in response
29
+ except Exception:
30
+ # 防御性处理:即使 ot 出现异常,也不阻塞主流程
31
+ return "!!!COMPLETE!!!" in response
32
+
33
+ def normalize_next_action(next_action: Any) -> str:
34
+ """
35
+ 规范化下一步动作为字符串:
36
+ - 如果是 Enum, 返回其 value(若为字符串)
37
+ - 如果是 str, 原样返回
38
+ - 其他情况返回空字符串
39
+ """
40
+ try:
41
+ if isinstance(next_action, Enum):
42
+ value = getattr(next_action, "value", None)
43
+ return value if isinstance(value, str) else ""
44
+ if isinstance(next_action, str):
45
+ return next_action
46
+ return ""
47
+ except Exception:
48
+ return ""
49
+
50
+ __all__ = ["join_prompts", "is_auto_complete", "normalize_next_action"]
@@ -12,6 +12,7 @@ from typing import List, Optional, Tuple
12
12
  import typer
13
13
 
14
14
  from jarvis.jarvis_agent import Agent
15
+ from jarvis.jarvis_agent.events import AFTER_TOOL_CALL
15
16
  from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
16
17
  from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
17
18
  from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
@@ -94,7 +95,7 @@ class CodeAgent:
94
95
  use_analysis=False, # 禁用分析
95
96
  )
96
97
 
97
- self.agent.set_after_tool_call_cb(self.after_tool_call_cb)
98
+ self.agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
98
99
 
99
100
  def _get_system_prompt(self) -> str:
100
101
  """获取代码工程师的系统提示词"""
@@ -598,7 +599,7 @@ class CodeAgent:
598
599
  except RuntimeError as e:
599
600
  return f"Error during execution: {str(e)}"
600
601
 
601
- def after_tool_call_cb(self, agent: Agent) -> None:
602
+ def _on_after_tool_call(self, agent: Agent, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
602
603
  """工具调用后回调函数。"""
603
604
  final_ret = ""
604
605
  diff = get_diff()
@@ -247,6 +247,14 @@
247
247
  },
248
248
  "default": []
249
249
  },
250
+ "JARVIS_AFTER_TOOL_CALL_CB_DIRS": {
251
+ "type": "array",
252
+ "description": "工具调用后回调函数实现目录",
253
+ "items": {
254
+ "type": "string"
255
+ },
256
+ "default": []
257
+ },
250
258
  "JARVIS_CENTRAL_METHODOLOGY_REPO": {
251
259
  "type": "string",
252
260
  "description": "中心方法论Git仓库地址,该仓库会自动添加到方法论加载路径中",
@@ -210,7 +210,7 @@ class SSEMcpClient(McpClient):
210
210
 
211
211
  # 调用已注册的处理器
212
212
  if method in self.notification_handlers:
213
- error_lines: list[str] = []
213
+ error_lines: List[str] = []
214
214
  for handler in self.notification_handlers[method]:
215
215
  try:
216
216
  handler(params)
@@ -63,7 +63,7 @@ class MemoryOrganizer:
63
63
  """加载指定类型的所有记忆"""
64
64
  memories = []
65
65
  memory_files = self._get_memory_files(memory_type)
66
- error_lines: list[str] = []
66
+ error_lines: List[str] = []
67
67
 
68
68
  for memory_file in memory_files:
69
69
  try:
@@ -388,8 +388,8 @@ tags:
388
388
  )
389
389
 
390
390
  # 删除原始记忆文件(先汇总日志,最后统一打印)
391
- info_lines: list[str] = []
392
- warn_lines: list[str] = []
391
+ info_lines: List[str] = []
392
+ warn_lines: List[str] = []
393
393
  for orig_memory in original_memories:
394
394
  if "file_path" in orig_memory:
395
395
  try:
@@ -428,7 +428,7 @@ tags:
428
428
  导出的记忆数量
429
429
  """
430
430
  all_memories = []
431
- progress_lines: list[str] = []
431
+ progress_lines: List[str] = []
432
432
 
433
433
  for memory_type in memory_types:
434
434
  progress_lines.append(f"正在导出 {memory_type} 类型的记忆...")
@@ -194,7 +194,7 @@ class KimiModel(BasePlatform):
194
194
  uploaded_files = []
195
195
  for index, file_path in enumerate(file_list, 1):
196
196
  file_name = os.path.basename(file_path)
197
- log_lines: list[str] = [f"处理文件 [{index}/{len(file_list)}]: {file_name}"]
197
+ log_lines: List[str] = [f"处理文件 [{index}/{len(file_list)}]: {file_name}"]
198
198
  try:
199
199
  mime_type, _ = mimetypes.guess_type(file_path)
200
200
  action = (
@@ -276,7 +276,7 @@ class TongyiPlatform(BasePlatform):
276
276
 
277
277
  for file_path in file_list:
278
278
  file_name = os.path.basename(file_path)
279
- log_lines: list[str] = []
279
+ log_lines: List[str] = []
280
280
  log_lines.append(f"上传文件 {file_name}")
281
281
  try:
282
282
  if not os.path.exists(file_path):
@@ -133,7 +133,7 @@ class YuanbaoPlatform(BasePlatform):
133
133
 
134
134
  for file_path in file_list:
135
135
  file_name = os.path.basename(file_path)
136
- log_lines: list[str] = []
136
+ log_lines: List[str] = []
137
137
  log_lines.append(f"上传文件 {file_name}")
138
138
  try:
139
139
  # 1. Prepare the file information
@@ -185,7 +185,7 @@ class ChromaRetriever:
185
185
  if not changed and not deleted:
186
186
  return
187
187
  # 为避免在循环中逐条打印,先拼接后统一打印
188
- lines: list[str] = []
188
+ lines: List[str] = []
189
189
  if changed:
190
190
  lines.append(
191
191
  f"检测到 {len(changed)} 个已索引文件发生变化,建议重新索引以保证检索准确性。"
@@ -247,7 +247,7 @@ class ChromaRetriever:
247
247
  return
248
248
 
249
249
  # 先处理删除
250
- delete_errors: list[str] = []
250
+ delete_errors: List[str] = []
251
251
  for src in deleted:
252
252
  try:
253
253
  self.collection.delete(where={"source": src}) # type: ignore[arg-type]
@@ -258,7 +258,7 @@ class ChromaRetriever:
258
258
 
259
259
  # 再处理变更(重建)
260
260
  docs_to_add: List[Document] = []
261
- rebuild_errors: list[str] = []
261
+ rebuild_errors: List[str] = []
262
262
  for src in changed:
263
263
  try:
264
264
  # 删除旧条目
@@ -604,8 +604,8 @@ class StatsManager:
604
604
 
605
605
  @staticmethod
606
606
  def _show_multiple_charts(
607
- start_time: datetime,
608
- end_time: datetime,
607
+ start_time: Optional[datetime],
608
+ end_time: Optional[datetime],
609
609
  aggregation: str,
610
610
  tags: Optional[Dict[str, str]],
611
611
  width: Optional[int] = None,
@@ -8,7 +8,7 @@ import json
8
8
  import os
9
9
  from datetime import datetime, timedelta
10
10
  from pathlib import Path
11
- from typing import Dict, List, Optional, Any
11
+ from typing import Dict, List, Optional, Any, Set
12
12
  from collections import defaultdict
13
13
  import sys
14
14
  import time
@@ -457,7 +457,7 @@ class StatsStorage:
457
457
  metrics_from_meta = set(meta.get("metrics", {}).keys())
458
458
 
459
459
  # 扫描所有数据文件获取实际存在的指标
460
- metrics_from_data: set[str] = set()
460
+ metrics_from_data: Set[str] = set()
461
461
  for data_file in self.data_dir.glob("stats_*.json"):
462
462
  try:
463
463
  data = self._load_json(data_file)
@@ -467,7 +467,7 @@ class StatsStorage:
467
467
  continue
468
468
 
469
469
  # 扫描总量缓存目录中已有的指标文件
470
- metrics_from_totals: set[str] = set()
470
+ metrics_from_totals: Set[str] = set()
471
471
  try:
472
472
  for f in self.totals_dir.glob("*"):
473
473
  if f.is_file():
@@ -14,7 +14,7 @@
14
14
  - 完善的错误处理和回滚机制
15
15
  - 严格的格式保持要求
16
16
  """
17
- from typing import Any, Dict
17
+ from typing import Any, Dict, List
18
18
 
19
19
  from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
20
20
 
@@ -122,7 +122,7 @@ class FileSearchReplaceTool:
122
122
 
123
123
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
124
124
 
125
- stdout_messages: list[str] = []
125
+ stdout_messages: List[str] = []
126
126
  overall_success = False
127
127
  file_results = []
128
128
 
@@ -167,7 +167,7 @@ class FileSearchReplaceTool:
167
167
  )
168
168
 
169
169
  # 整合所有错误信息到stderr
170
- all_stderr: list[str] = []
170
+ all_stderr: List[str] = []
171
171
  for file_result in file_results:
172
172
  if not file_result["success"]:
173
173
  all_stderr.append(f"文件 {file_result['file']} 处理失败: {file_result['stderr']}")
@@ -2,7 +2,7 @@
2
2
  import os
3
3
  import tempfile
4
4
  from pathlib import Path
5
- from typing import Any, Dict
5
+ from typing import Any, Dict, List
6
6
 
7
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
8
8
 
@@ -74,7 +74,7 @@ class ScriptTool:
74
74
  stream.feed(data)
75
75
 
76
76
  # 清理每行右侧空格,并过滤空行
77
- cleaned: list[str] = []
77
+ cleaned: List[str] = []
78
78
  for y in range(screen.lines):
79
79
  line = screen.buffer[y]
80
80
  stripped = "".join(char.data for char in line.values()).rstrip()
@@ -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)
@@ -8,7 +8,7 @@ sub_agent 工具
8
8
  - 继承父 Agent 的部分配置:model_group、input_handler、execute_tool_confirm、multiline_inputer;其他参数需显式提供
9
9
  - 子Agent必须自动完成(auto_complete=True)且需要summary(need_summary=True)
10
10
  """
11
- from typing import Any, Dict
11
+ from typing import Any, Dict, List
12
12
  import json
13
13
 
14
14
  from jarvis.jarvis_agent import Agent
@@ -107,7 +107,7 @@ class SubAgentTool:
107
107
 
108
108
  # 解析可用工具列表(支持数组或以逗号分隔的字符串)
109
109
  _use_tools = args.get("use_tools", None)
110
- use_tools: list[str] = []
110
+ use_tools: List[str] = []
111
111
  if isinstance(_use_tools, list):
112
112
  use_tools = [str(x).strip() for x in _use_tools if str(x).strip()]
113
113
  elif isinstance(_use_tools, str):
@@ -8,7 +8,7 @@ sub_code_agent 工具
8
8
  - 不依赖父 Agent,所有配置使用系统默认与全局变量
9
9
  - 子Agent必须自动完成(auto_complete=True)且需要summary(need_summary=True)
10
10
  """
11
- from typing import Any, Dict
11
+ from typing import Any, Dict, List
12
12
  import json
13
13
 
14
14
  from jarvis.jarvis_code_agent.code_agent import CodeAgent
@@ -85,7 +85,7 @@ class SubCodeAgentTool:
85
85
  except Exception:
86
86
  parent_agent = None
87
87
  model_group = None
88
- use_tools: list[str] = []
88
+ use_tools: List[str] = []
89
89
  try:
90
90
  if parent_agent is not None:
91
91
  if getattr(parent_agent, "model", None):
@@ -285,7 +285,7 @@ def get_pretty_output() -> bool:
285
285
  if platform.system() == "Windows":
286
286
  return False
287
287
 
288
- return GLOBAL_CONFIG_DATA.get("JARVIS_PRETTY_OUTPUT", False)
288
+ return GLOBAL_CONFIG_DATA.get("JARVIS_PRETTY_OUTPUT", True)
289
289
 
290
290
 
291
291
  def is_use_methodology() -> bool:
@@ -378,6 +378,20 @@ def get_roles_dirs() -> List[str]:
378
378
  ]
379
379
 
380
380
 
381
+ def get_after_tool_call_cb_dirs() -> List[str]:
382
+ """
383
+ 获取工具调用后回调函数实现目录。
384
+
385
+ 返回:
386
+ List[str]: 工具调用后回调函数实现目录列表
387
+ """
388
+ return [
389
+ os.path.expanduser(os.path.expandvars(str(p)))
390
+ for p in GLOBAL_CONFIG_DATA.get("JARVIS_AFTER_TOOL_CALL_CB_DIRS", [])
391
+ if p
392
+ ]
393
+
394
+
381
395
  def get_central_methodology_repo() -> str:
382
396
  """
383
397
  获取中心方法论Git仓库地址。
@@ -423,9 +437,9 @@ def is_force_save_memory() -> bool:
423
437
  获取是否强制保存记忆。
424
438
 
425
439
  返回:
426
- bool: 如果强制保存记忆则返回True,默认为True
440
+ bool: 如果强制保存记忆则返回True,默认为False
427
441
  """
428
- return GLOBAL_CONFIG_DATA.get("JARVIS_FORCE_SAVE_MEMORY", True) is True
442
+ return GLOBAL_CONFIG_DATA.get("JARVIS_FORCE_SAVE_MEMORY", False) is True
429
443
 
430
444
 
431
445
  def is_enable_static_analysis() -> bool:
@@ -2,10 +2,10 @@
2
2
  """FZF selector utility."""
3
3
  import shutil
4
4
  import subprocess
5
- from typing import List, Optional, Union
5
+ from typing import List, Optional, Union, Dict, Any, cast
6
6
 
7
7
  def fzf_select(
8
- options: Union[List[str], List[dict]],
8
+ options: Union[List[str], List[Dict[str, Any]]],
9
9
  prompt: str = "SELECT> ",
10
10
  key: Optional[str] = None,
11
11
  ) -> Optional[str]:
@@ -29,7 +29,8 @@ def fzf_select(
29
29
  if isinstance(options[0], dict):
30
30
  if key is None:
31
31
  raise ValueError("A key must be provided for a list of dicts.")
32
- input_lines = [str(item.get(key, "")) for item in options]
32
+ options_dict = cast(List[Dict[str, Any]], options)
33
+ input_lines = [str(item.get(key, "")) for item in options_dict]
33
34
  else:
34
35
  input_lines = [str(item) for item in options]
35
36