jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__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 (181) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +458 -152
  3. jarvis/jarvis_agent/agent_manager.py +17 -13
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +329 -0
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +628 -55
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +34 -10
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +105 -9
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +20 -22
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  31. jarvis/jarvis_agent/task_analyzer.py +31 -6
  32. jarvis/jarvis_agent/task_manager.py +11 -27
  33. jarvis/jarvis_agent/tool_executor.py +2 -3
  34. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  35. jarvis/jarvis_agent/utils.py +5 -1
  36. jarvis/jarvis_agent/web_bridge.py +189 -0
  37. jarvis/jarvis_agent/web_output_sink.py +53 -0
  38. jarvis/jarvis_agent/web_server.py +786 -0
  39. jarvis/jarvis_c2rust/__init__.py +26 -0
  40. jarvis/jarvis_c2rust/cli.py +575 -0
  41. jarvis/jarvis_c2rust/collector.py +250 -0
  42. jarvis/jarvis_c2rust/constants.py +26 -0
  43. jarvis/jarvis_c2rust/library_replacer.py +1254 -0
  44. jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
  45. jarvis/jarvis_c2rust/loaders.py +207 -0
  46. jarvis/jarvis_c2rust/models.py +28 -0
  47. jarvis/jarvis_c2rust/optimizer.py +2157 -0
  48. jarvis/jarvis_c2rust/scanner.py +1681 -0
  49. jarvis/jarvis_c2rust/transpiler.py +2983 -0
  50. jarvis/jarvis_c2rust/utils.py +385 -0
  51. jarvis/jarvis_code_agent/build_validation_config.py +132 -0
  52. jarvis/jarvis_code_agent/code_agent.py +1371 -220
  53. jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
  54. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
  60. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
  61. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
  62. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
  63. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
  64. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
  65. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
  66. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
  67. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
  68. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  69. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
  70. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  71. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  72. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  73. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  74. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  75. jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
  76. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
  77. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
  78. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
  79. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  80. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  81. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
  82. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
  83. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  84. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
  85. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  86. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
  87. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
  88. jarvis/jarvis_code_agent/lint.py +501 -8
  89. jarvis/jarvis_code_agent/utils.py +141 -0
  90. jarvis/jarvis_code_analysis/code_review.py +493 -584
  91. jarvis/jarvis_data/config_schema.json +128 -12
  92. jarvis/jarvis_git_squash/main.py +4 -5
  93. jarvis/jarvis_git_utils/git_commiter.py +82 -75
  94. jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
  95. jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
  96. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  97. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  98. jarvis/jarvis_methodology/main.py +32 -48
  99. jarvis/jarvis_multi_agent/__init__.py +287 -55
  100. jarvis/jarvis_multi_agent/main.py +36 -4
  101. jarvis/jarvis_platform/base.py +524 -202
  102. jarvis/jarvis_platform/human.py +7 -8
  103. jarvis/jarvis_platform/kimi.py +30 -36
  104. jarvis/jarvis_platform/openai.py +88 -25
  105. jarvis/jarvis_platform/registry.py +26 -10
  106. jarvis/jarvis_platform/tongyi.py +24 -25
  107. jarvis/jarvis_platform/yuanbao.py +32 -43
  108. jarvis/jarvis_platform_manager/main.py +66 -77
  109. jarvis/jarvis_platform_manager/service.py +8 -13
  110. jarvis/jarvis_rag/cli.py +53 -55
  111. jarvis/jarvis_rag/embedding_manager.py +13 -18
  112. jarvis/jarvis_rag/llm_interface.py +8 -9
  113. jarvis/jarvis_rag/query_rewriter.py +10 -21
  114. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  115. jarvis/jarvis_rag/reranker.py +4 -5
  116. jarvis/jarvis_rag/retriever.py +28 -30
  117. jarvis/jarvis_sec/__init__.py +305 -0
  118. jarvis/jarvis_sec/agents.py +143 -0
  119. jarvis/jarvis_sec/analysis.py +276 -0
  120. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  121. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  122. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  123. jarvis/jarvis_sec/cli.py +139 -0
  124. jarvis/jarvis_sec/clustering.py +1439 -0
  125. jarvis/jarvis_sec/file_manager.py +427 -0
  126. jarvis/jarvis_sec/parsers.py +73 -0
  127. jarvis/jarvis_sec/prompts.py +268 -0
  128. jarvis/jarvis_sec/report.py +336 -0
  129. jarvis/jarvis_sec/review.py +453 -0
  130. jarvis/jarvis_sec/status.py +264 -0
  131. jarvis/jarvis_sec/types.py +20 -0
  132. jarvis/jarvis_sec/utils.py +499 -0
  133. jarvis/jarvis_sec/verification.py +848 -0
  134. jarvis/jarvis_sec/workflow.py +226 -0
  135. jarvis/jarvis_smart_shell/main.py +38 -87
  136. jarvis/jarvis_stats/cli.py +2 -2
  137. jarvis/jarvis_stats/stats.py +8 -8
  138. jarvis/jarvis_stats/storage.py +15 -21
  139. jarvis/jarvis_stats/visualizer.py +1 -1
  140. jarvis/jarvis_tools/clear_memory.py +3 -20
  141. jarvis/jarvis_tools/cli/main.py +21 -23
  142. jarvis/jarvis_tools/edit_file.py +1019 -132
  143. jarvis/jarvis_tools/execute_script.py +83 -25
  144. jarvis/jarvis_tools/file_analyzer.py +6 -9
  145. jarvis/jarvis_tools/generate_new_tool.py +14 -21
  146. jarvis/jarvis_tools/lsp_client.py +1552 -0
  147. jarvis/jarvis_tools/methodology.py +2 -3
  148. jarvis/jarvis_tools/read_code.py +1736 -35
  149. jarvis/jarvis_tools/read_symbols.py +140 -0
  150. jarvis/jarvis_tools/read_webpage.py +12 -13
  151. jarvis/jarvis_tools/registry.py +427 -200
  152. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  153. jarvis/jarvis_tools/rewrite_file.py +72 -158
  154. jarvis/jarvis_tools/save_memory.py +3 -15
  155. jarvis/jarvis_tools/search_web.py +18 -18
  156. jarvis/jarvis_tools/sub_agent.py +36 -43
  157. jarvis/jarvis_tools/sub_code_agent.py +25 -26
  158. jarvis/jarvis_tools/virtual_tty.py +55 -33
  159. jarvis/jarvis_utils/clipboard.py +7 -10
  160. jarvis/jarvis_utils/config.py +232 -45
  161. jarvis/jarvis_utils/embedding.py +8 -5
  162. jarvis/jarvis_utils/fzf.py +8 -8
  163. jarvis/jarvis_utils/git_utils.py +225 -36
  164. jarvis/jarvis_utils/globals.py +3 -3
  165. jarvis/jarvis_utils/http.py +1 -1
  166. jarvis/jarvis_utils/input.py +99 -48
  167. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  168. jarvis/jarvis_utils/methodology.py +52 -48
  169. jarvis/jarvis_utils/utils.py +819 -491
  170. jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
  171. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  172. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
  173. jarvis/jarvis_agent/config.py +0 -92
  174. jarvis/jarvis_agent/edit_file_handler.py +0 -296
  175. jarvis/jarvis_platform/ai8.py +0 -332
  176. jarvis/jarvis_tools/ask_user.py +0 -54
  177. jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
  178. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  179. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  180. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  181. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -6,8 +6,7 @@
6
6
 
7
7
  from jarvis.jarvis_utils.globals import get_interrupt, set_interrupt
8
8
 
9
- from jarvis.jarvis_agent.prompts import TASK_ANALYSIS_PROMPT
10
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
+ from jarvis.jarvis_agent.prompts import get_task_analysis_prompt
11
10
  from jarvis.jarvis_agent.utils import join_prompts
12
11
  from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL, BEFORE_SUMMARY, TASK_COMPLETED
13
12
 
@@ -46,7 +45,7 @@ class TaskAnalyzer:
46
45
  self._process_analysis_loop()
47
46
 
48
47
  except Exception:
49
- PrettyOutput.print("分析失败", OutputType.ERROR)
48
+ print("分析失败")
50
49
  finally:
51
50
  # 标记已完成一次分析,避免事件回调重复执行
52
51
  self._analysis_done = True
@@ -57,7 +56,30 @@ class TaskAnalyzer:
57
56
 
58
57
  def _prepare_analysis_prompt(self, satisfaction_feedback: str) -> str:
59
58
  """准备分析提示"""
60
- return join_prompts([TASK_ANALYSIS_PROMPT, satisfaction_feedback])
59
+ # 检查是否有 save_memory 工具(工具可用性)
60
+ has_save_memory = False
61
+ # 检查是否有 generate_new_tool 工具
62
+ has_generate_new_tool = False
63
+ try:
64
+ tool_registry = self.agent.get_tool_registry()
65
+ if tool_registry:
66
+ # 检查 save_memory 工具
67
+ save_memory_tool = tool_registry.get_tool("save_memory")
68
+ has_save_memory = save_memory_tool is not None
69
+
70
+ # 检查 generate_new_tool 工具
71
+ generate_tool = tool_registry.get_tool("generate_new_tool")
72
+ has_generate_new_tool = generate_tool is not None
73
+ except Exception:
74
+ pass
75
+
76
+ # 根据配置获取相应的提示词
77
+ analysis_prompt = get_task_analysis_prompt(
78
+ has_save_memory=has_save_memory,
79
+ has_generate_new_tool=has_generate_new_tool
80
+ )
81
+
82
+ return join_prompts([analysis_prompt, satisfaction_feedback])
61
83
 
62
84
  def _process_analysis_loop(self):
63
85
  """处理分析循环"""
@@ -128,7 +150,7 @@ class TaskAnalyzer:
128
150
 
129
151
  def _handle_interrupt_with_tool_calls(self, user_input: str) -> str:
130
152
  """处理有工具调用时的中断"""
131
- if self.agent.user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
153
+ if self.agent.confirm_callback("检测到有工具调用,是否继续处理工具调用?", True):
132
154
  return join_prompts([
133
155
  f"被用户中断,用户补充信息为:{user_input}",
134
156
  "用户同意继续工具调用。"
@@ -144,7 +166,7 @@ class TaskAnalyzer:
144
166
  satisfaction_feedback = ""
145
167
 
146
168
  if not auto_completed and self.agent.use_analysis:
147
- if self.agent.user_confirm("您对本次任务的完成是否满意?", True):
169
+ if self.agent.confirm_callback("您对本次任务的完成是否满意?", True):
148
170
  satisfaction_feedback = "用户对本次任务的完成表示满意。"
149
171
  else:
150
172
  feedback = self.agent._multiline_input(
@@ -158,6 +180,9 @@ class TaskAnalyzer:
158
180
  satisfaction_feedback = (
159
181
  "用户对本次任务的完成不满意,未提供具体反馈意见。"
160
182
  )
183
+ elif auto_completed and self.agent.use_analysis:
184
+ # 自动完成模式下,仍然执行分析,但不收集用户反馈
185
+ satisfaction_feedback = "任务已自动完成,无需用户反馈。"
161
186
 
162
187
  return satisfaction_feedback
163
188
 
@@ -9,8 +9,6 @@ from rich.table import Table
9
9
  from rich.console import Console
10
10
 
11
11
  from jarvis.jarvis_agent import (
12
- OutputType,
13
- PrettyOutput,
14
12
  get_multiline_input,
15
13
  user_confirm,
16
14
  )
@@ -31,9 +29,7 @@ class TaskManager:
31
29
  data_dir = get_data_dir()
32
30
  pre_command_path = os.path.join(data_dir, "pre-command")
33
31
  if os.path.exists(pre_command_path):
34
- PrettyOutput.print(
35
- f"从{pre_command_path}加载预定义任务...", OutputType.INFO
36
- )
32
+ print(f"ℹ️ 从{pre_command_path}加载预定义任务...")
37
33
  try:
38
34
  with open(
39
35
  pre_command_path, "r", encoding="utf-8", errors="ignore"
@@ -43,19 +39,15 @@ class TaskManager:
43
39
  for name, desc in user_tasks.items():
44
40
  if desc:
45
41
  tasks[str(name)] = str(desc)
46
- PrettyOutput.print(
47
- f"预定义任务加载完成 {pre_command_path}", OutputType.SUCCESS
48
- )
42
+ print(f"✅ 预定义任务加载完成 {pre_command_path}")
49
43
  except (yaml.YAMLError, OSError):
50
- PrettyOutput.print(
51
- f"预定义任务加载失败 {pre_command_path}", OutputType.ERROR
52
- )
44
+ print(f"❌ 预定义任务加载失败 {pre_command_path}")
53
45
 
54
46
  # Check .jarvis/pre-command in current directory
55
47
  pre_command_path = ".jarvis/pre-command"
56
48
  if os.path.exists(pre_command_path):
57
49
  abs_path = os.path.abspath(pre_command_path)
58
- PrettyOutput.print(f"从{abs_path}加载预定义任务...", OutputType.INFO)
50
+ print(f"ℹ️ 从{abs_path}加载预定义任务...")
59
51
  try:
60
52
  with open(
61
53
  pre_command_path, "r", encoding="utf-8", errors="ignore"
@@ -65,13 +57,9 @@ class TaskManager:
65
57
  for name, desc in local_tasks.items():
66
58
  if desc:
67
59
  tasks[str(name)] = str(desc)
68
- PrettyOutput.print(
69
- f"预定义任务加载完成 {pre_command_path}", OutputType.SUCCESS
70
- )
60
+ print(f"✅ 预定义任务加载完成 {pre_command_path}")
71
61
  except (yaml.YAMLError, OSError):
72
- PrettyOutput.print(
73
- f"预定义任务加载失败 {pre_command_path}", OutputType.ERROR
74
- )
62
+ print(f"❌ 预定义任务加载失败 {pre_command_path}")
75
63
 
76
64
  return tasks
77
65
 
@@ -89,7 +77,7 @@ class TaskManager:
89
77
  for i, name in enumerate(task_names, 1):
90
78
  table.add_row(str(i), name)
91
79
  Console().print(table)
92
- PrettyOutput.print("[0] 跳过预定义任务", OutputType.INFO)
80
+ print("ℹ️ [0] 跳过预定义任务")
93
81
 
94
82
  # Try fzf selection first (with numbered options and a skip option)
95
83
  fzf_list = [f"{0:>3} | 跳过预定义任务"] + [
@@ -104,7 +92,7 @@ class TaskManager:
104
92
  return ""
105
93
  if 1 <= idx <= len(task_names):
106
94
  selected_task = tasks[task_names[idx - 1]]
107
- PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
95
+ print(f"ℹ️ 将要执行任务:\n {selected_task}")
108
96
  # 询问是否需要补充信息
109
97
  need_additional = user_confirm("需要为此任务添加补充信息吗?", default=False)
110
98
  if need_additional:
@@ -129,9 +117,7 @@ class TaskManager:
129
117
  return ""
130
118
  if 1 <= choice <= len(task_names):
131
119
  selected_task = tasks[task_names[choice - 1]]
132
- PrettyOutput.print(
133
- f"将要执行任务:\n {selected_task}", OutputType.INFO
134
- )
120
+ print(f"ℹ️ 将要执行任务:\n {selected_task}")
135
121
  # 询问是否需要补充信息
136
122
  need_additional = user_confirm(
137
123
  "需要为此任务添加补充信息吗?", default=False
@@ -144,11 +130,9 @@ class TaskManager:
144
130
  f"补充信息:\n{additional_input}"
145
131
  ])
146
132
  return selected_task
147
- PrettyOutput.print(
148
- "无效的选择。请选择列表中的一个号码。", OutputType.WARNING
149
- )
133
+ print("⚠️ 无效的选择。请选择列表中的一个号码。")
150
134
 
151
135
  except (KeyboardInterrupt, EOFError):
152
136
  return ""
153
137
  except ValueError as val_err:
154
- PrettyOutput.print(f"选择任务失败: {str(val_err)}", OutputType.ERROR)
138
+ print(f"选择任务失败: {str(val_err)}")
@@ -2,7 +2,6 @@
2
2
  from typing import Any, Tuple, TYPE_CHECKING
3
3
 
4
4
  from jarvis.jarvis_utils.input import user_confirm
5
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
5
 
7
6
  if TYPE_CHECKING:
8
7
  from jarvis.jarvis_agent import Agent
@@ -31,7 +30,7 @@ def execute_tool_call(response: str, agent: "Agent") -> Tuple[bool, Any]:
31
30
  f"操作失败:检测到多个操作。一次只能执行一个操作。"
32
31
  f"尝试执行的操作:{', '.join([handler.name() for handler in tool_list])}"
33
32
  )
34
- PrettyOutput.print(error_message, OutputType.WARNING)
33
+ print(f"⚠️ {error_message}")
35
34
  return False, error_message
36
35
 
37
36
  if not tool_list:
@@ -47,7 +46,7 @@ def execute_tool_call(response: str, agent: "Agent") -> Tuple[bool, Any]:
47
46
  print(f"✅ {tool_to_execute.name()}执行完成")
48
47
  return result
49
48
  except Exception as e:
50
- PrettyOutput.print(f"工具执行失败: {str(e)}", OutputType.ERROR)
49
+ print(f"工具执行失败: {str(e)}")
51
50
  return False, str(e)
52
51
 
53
52
  return False, ""
@@ -7,7 +7,7 @@ from typing import List, Dict, Any, Set
7
7
 
8
8
  import typer
9
9
 
10
- from jarvis.jarvis_agent import OutputType, PrettyOutput, user_confirm
10
+ from jarvis.jarvis_agent import user_confirm
11
11
  from jarvis.jarvis_agent.share_manager import ShareManager
12
12
  from jarvis.jarvis_utils.config import get_central_tool_repo, get_data_dir
13
13
 
@@ -18,13 +18,8 @@ class ToolShareManager(ShareManager):
18
18
  def __init__(self):
19
19
  central_repo = get_central_tool_repo()
20
20
  if not central_repo:
21
- PrettyOutput.print(
22
- "错误:未配置中心工具仓库(JARVIS_CENTRAL_TOOL_REPO)",
23
- OutputType.ERROR,
24
- )
25
- PrettyOutput.print(
26
- "请在配置文件中设置中心工具仓库的Git地址", OutputType.INFO
27
- )
21
+ print("❌ 错误:未配置中心工具仓库(JARVIS_CENTRAL_TOOL_REPO)")
22
+ print("ℹ️ 请在配置文件中设置中心工具仓库的Git地址")
28
23
  raise typer.Exit(code=1)
29
24
 
30
25
  super().__init__(central_repo, "central_tool_repo")
@@ -52,10 +47,7 @@ class ToolShareManager(ShareManager):
52
47
  # 只从数据目录的tools目录获取工具
53
48
  local_tools_dir = os.path.join(get_data_dir(), "tools")
54
49
  if not os.path.exists(local_tools_dir):
55
- PrettyOutput.print(
56
- f"本地工具目录不存在: {local_tools_dir}",
57
- OutputType.WARNING,
58
- )
50
+ print(f"⚠️ 本地工具目录不存在: {local_tools_dir}")
59
51
  return []
60
52
 
61
53
  # 收集本地工具文件(排除已存在的)
@@ -84,7 +76,8 @@ class ToolShareManager(ShareManager):
84
76
  share_list = ["\n将要分享以下工具到中心仓库(注意:文件将被移动而非复制):"]
85
77
  for tool in resources:
86
78
  share_list.append(f"- {tool['tool_name']} ({tool['filename']})")
87
- PrettyOutput.print("\n".join(share_list), OutputType.WARNING)
79
+ joined_list = '\n'.join(share_list)
80
+ print(f"⚠️ {joined_list}")
88
81
 
89
82
  if not user_confirm("确认移动这些工具到中心仓库吗?(原文件将被删除)"):
90
83
  return []
@@ -108,10 +101,7 @@ class ToolShareManager(ShareManager):
108
101
  # 获取本地资源
109
102
  local_resources = self.get_local_resources()
110
103
  if not local_resources:
111
- PrettyOutput.print(
112
- "没有找到新的工具文件(所有工具可能已存在于中心仓库)",
113
- OutputType.WARNING,
114
- )
104
+ print("⚠️ 没有找到新的工具文件(所有工具可能已存在于中心仓库)")
115
105
  return
116
106
 
117
107
  # 选择要分享的资源
@@ -123,17 +113,15 @@ class ToolShareManager(ShareManager):
123
113
  moved_list = self.share_resources(selected_resources)
124
114
  if moved_list:
125
115
  # 一次性显示所有移动结果
126
- PrettyOutput.print("\n".join(moved_list), OutputType.SUCCESS)
116
+ joined_moved = '\n'.join(moved_list)
117
+ print(f"✅ {joined_moved}")
127
118
 
128
119
  # 提交并推送
129
120
  self.commit_and_push(len(selected_resources))
130
121
 
131
- PrettyOutput.print("\n工具已成功分享到中心仓库!", OutputType.SUCCESS)
132
- PrettyOutput.print(
133
- f"原文件已从 {os.path.join(get_data_dir(), 'tools')} 移动到中心仓库",
134
- OutputType.INFO,
135
- )
122
+ print("工具已成功分享到中心仓库!")
123
+ print(f"ℹ️ 原文件已从 {os.path.join(get_data_dir(), 'tools')} 移动到中心仓库")
136
124
 
137
125
  except Exception as e:
138
- PrettyOutput.print(f"分享工具时出错: {str(e)}", OutputType.ERROR)
126
+ print(f"分享工具时出错: {str(e)}")
139
127
  raise typer.Exit(code=1)
@@ -16,7 +16,11 @@ def join_prompts(parts: Iterable[str]) -> str:
16
16
  - 使用两个换行分隔
17
17
  - 不进行额外 strip,保持调用方原样语义
18
18
  """
19
- non_empty: List[str] = [p for p in parts if p]
19
+ try:
20
+ non_empty: List[str] = [p for p in parts if isinstance(p, str) and p]
21
+ except Exception:
22
+ # 防御性处理:若 parts 不可迭代或出现异常,直接返回空字符串
23
+ return ""
20
24
  return "\n\n".join(non_empty)
21
25
 
22
26
  def is_auto_complete(response: str) -> bool:
@@ -0,0 +1,189 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ WebBridge: WebSocket 交互桥
4
+ - 提供线程安全的广播能力(后续由 WebSocket 服务注册发送函数)
5
+ - 提供阻塞式的多行输入与确认请求(通过 request_* 发起请求,等待浏览器端响应)
6
+ - 适配 Agent 的输入注入接口:web_multiline_input / web_user_confirm
7
+ - 事件约定(发往前端,均为 JSON 对象):
8
+ * {"type":"input_request","mode":"multiline","tip": "...","print_on_empty": true/false,"request_id":"..."}
9
+ * {"type":"confirm_request","tip":"...","default": true/false,"request_id":"..."}
10
+ 后续输出事件由输出Sink负责(使用 PrettyOutput.add_sink 接入),不在本桥内实现。
11
+ - 事件约定(来自前端):
12
+ * {"type":"user_input","request_id":"...","text":"..."}
13
+ * {"type":"confirm_response","request_id":"...","value": true/false}
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import threading
18
+ import uuid
19
+ from queue import Queue, Empty
20
+ from typing import Callable, Dict, Optional, Set, Any
21
+
22
+ DEFAULT_WAIT_TIMEOUT = None # 阻塞等待直到收到响应(可按需改为秒数)
23
+
24
+
25
+ class WebBridge:
26
+ """
27
+ 线程安全的 WebSocket 交互桥。
28
+ - 维护一组客户端发送函数(由Web服务注册),用于广播事件
29
+ - 维护挂起的输入/确认请求队列,按 request_id 匹配响应
30
+ """
31
+
32
+ _instance_lock = threading.Lock()
33
+ _instance: Optional["WebBridge"] = None
34
+
35
+ def __init__(self) -> None:
36
+ self._clients: Set[Callable[[Dict[str, Any]], None]] = set()
37
+ self._clients_lock = threading.Lock()
38
+
39
+ # 按 request_id 等待的阻塞队列
40
+ self._pending_inputs: Dict[str, Queue] = {}
41
+ self._pending_confirms: Dict[str, Queue] = {}
42
+ self._pending_lock = threading.Lock()
43
+
44
+ @classmethod
45
+ def instance(cls) -> "WebBridge":
46
+ with cls._instance_lock:
47
+ if cls._instance is None:
48
+ cls._instance = WebBridge()
49
+ return cls._instance
50
+
51
+ # ---------------------------
52
+ # 客户端管理与广播
53
+ # ---------------------------
54
+ def add_client(self, sender: Callable[[Dict[str, Any]], None]) -> None:
55
+ """
56
+ 注册一个客户端发送函数。发送函数需接受一个 dict,并自行完成异步发送。
57
+ 例如在 FastAPI/WS 中包装成 enqueue 到事件循环的任务。
58
+ """
59
+ with self._clients_lock:
60
+ self._clients.add(sender)
61
+
62
+ def remove_client(self, sender: Callable[[Dict[str, Any]], None]) -> None:
63
+ with self._clients_lock:
64
+ if sender in self._clients:
65
+ self._clients.remove(sender)
66
+
67
+ def broadcast(self, payload: Dict[str, Any]) -> None:
68
+ """
69
+ 广播一条消息给所有客户端。失败的客户端不影响其他客户端。
70
+ """
71
+ with self._clients_lock:
72
+ targets = list(self._clients)
73
+ for send in targets:
74
+ try:
75
+ send(payload)
76
+ except Exception:
77
+ # 静默忽略单个客户端的发送异常
78
+ pass
79
+
80
+ # ---------------------------
81
+ # 输入/确认 请求-响应 管理
82
+ # ---------------------------
83
+ def request_multiline_input(self, tip: str, print_on_empty: bool = True, timeout: Optional[float] = DEFAULT_WAIT_TIMEOUT) -> str:
84
+ """
85
+ 发起一个多行输入请求并阻塞等待浏览器返回。
86
+ 返回用户输入的文本(可能为空字符串,表示取消)。
87
+ """
88
+ req_id = uuid.uuid4().hex
89
+ q: Queue = Queue(maxsize=1)
90
+ with self._pending_lock:
91
+ self._pending_inputs[req_id] = q
92
+
93
+ self.broadcast({
94
+ "type": "input_request",
95
+ "mode": "multiline",
96
+ "tip": tip,
97
+ "print_on_empty": bool(print_on_empty),
98
+ "request_id": req_id,
99
+ })
100
+
101
+ try:
102
+ if timeout is None:
103
+ result = q.get() # 阻塞直到有结果
104
+ else:
105
+ result = q.get(timeout=timeout)
106
+ except Empty:
107
+ result = "" # 超时回退为空
108
+ finally:
109
+ with self._pending_lock:
110
+ self._pending_inputs.pop(req_id, None)
111
+
112
+ # 规范化为字符串
113
+ return str(result or "")
114
+
115
+ def request_confirm(self, tip: str, default: bool = True, timeout: Optional[float] = DEFAULT_WAIT_TIMEOUT) -> bool:
116
+ """
117
+ 发起一个确认请求并阻塞等待浏览器返回。
118
+ 返回 True/False,若超时则回退为 default。
119
+ """
120
+ req_id = uuid.uuid4().hex
121
+ q: Queue = Queue(maxsize=1)
122
+ with self._pending_lock:
123
+ self._pending_confirms[req_id] = q
124
+
125
+ self.broadcast({
126
+ "type": "confirm_request",
127
+ "tip": tip,
128
+ "default": bool(default),
129
+ "request_id": req_id,
130
+ })
131
+
132
+ try:
133
+ if timeout is None:
134
+ result = q.get()
135
+ else:
136
+ result = q.get(timeout=timeout)
137
+ except Empty:
138
+ result = default
139
+ finally:
140
+ with self._pending_lock:
141
+ self._pending_confirms.pop(req_id, None)
142
+
143
+ return bool(result)
144
+
145
+ # ---------------------------
146
+ # 由 Web 服务回调:注入用户响应
147
+ # ---------------------------
148
+ def post_user_input(self, request_id: str, text: str) -> None:
149
+ """
150
+ 注入浏览器端的多行输入响应。
151
+ """
152
+ with self._pending_lock:
153
+ q = self._pending_inputs.get(request_id)
154
+ if q:
155
+ try:
156
+ q.put_nowait(text)
157
+ except Exception:
158
+ pass
159
+
160
+ def post_confirm(self, request_id: str, value: bool) -> None:
161
+ """
162
+ 注入浏览器端的确认响应。
163
+ """
164
+ with self._pending_lock:
165
+ q = self._pending_confirms.get(request_id)
166
+ if q:
167
+ try:
168
+ q.put_nowait(bool(value))
169
+ except Exception:
170
+ pass
171
+
172
+
173
+ # ---------------------------
174
+ # 供 Agent 注入的输入函数
175
+ # ---------------------------
176
+ def web_multiline_input(tip: str, print_on_empty: bool = True) -> str:
177
+ """
178
+ 适配 Agent.multiline_inputer 签名的多行输入函数。
179
+ 在 Web 模式下被注入到 Agent,转由浏览器端输入。
180
+ """
181
+ return WebBridge.instance().request_multiline_input(tip, print_on_empty)
182
+
183
+
184
+ def web_user_confirm(tip: str, default: bool = True) -> bool:
185
+ """
186
+ 适配 Agent.confirm_callback 签名的确认函数。
187
+ 在 Web 模式下被注入到 Agent,转由浏览器端确认。
188
+ """
189
+ return WebBridge.instance().request_confirm(tip, default)
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ WebSocketOutputSink: 将 PrettyOutput 的输出事件通过 WebBridge 广播给前端(WebSocket 客户端)
4
+
5
+ 用法:
6
+ - 在 Web 模式启动时,注册该 Sink:
7
+ from jarvis.jarvis_utils.output import PrettyOutput
8
+ from jarvis.jarvis_agent.web_output_sink import WebSocketOutputSink
9
+ PrettyOutput.add_sink(WebSocketOutputSink())
10
+
11
+ - Web 端收到的消息结构:
12
+ {
13
+ "type": "output",
14
+ "payload": {
15
+ "text": "...",
16
+ "output_type": "INFO" | "ERROR" | ...,
17
+ "timestamp": true/false,
18
+ "lang": "markdown" | "python" | ... | null,
19
+ "traceback": false,
20
+ "section": null | "标题",
21
+ "context": { ... } | null
22
+ }
23
+ }
24
+ """
25
+ from __future__ import annotations
26
+
27
+ from typing import Any, Dict
28
+
29
+ from jarvis.jarvis_utils.output import OutputSink, OutputEvent
30
+ from jarvis.jarvis_agent.web_bridge import WebBridge
31
+
32
+
33
+ class WebSocketOutputSink(OutputSink):
34
+ """将输出事件广播到 WebSocket 前端的 OutputSink 实现。"""
35
+
36
+ def emit(self, event: OutputEvent) -> None:
37
+ try:
38
+ payload: Dict[str, Any] = {
39
+ "type": "output",
40
+ "payload": {
41
+ "text": event.text,
42
+ "output_type": event.output_type.value,
43
+ "timestamp": bool(event.timestamp),
44
+ "lang": event.lang,
45
+ "traceback": bool(event.traceback),
46
+ "section": event.section,
47
+ "context": event.context,
48
+ },
49
+ }
50
+ WebBridge.instance().broadcast(payload)
51
+ except Exception:
52
+ # 广播过程中的异常不应影响其他输出后端
53
+ pass