jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +1143 -245
  3. jarvis/jarvis_agent/agent_manager.py +97 -0
  4. jarvis/jarvis_agent/builtin_input_handler.py +12 -10
  5. jarvis/jarvis_agent/config_editor.py +57 -0
  6. jarvis/jarvis_agent/edit_file_handler.py +392 -99
  7. jarvis/jarvis_agent/event_bus.py +48 -0
  8. jarvis/jarvis_agent/events.py +157 -0
  9. jarvis/jarvis_agent/file_context_handler.py +79 -0
  10. jarvis/jarvis_agent/file_methodology_manager.py +117 -0
  11. jarvis/jarvis_agent/jarvis.py +1117 -147
  12. jarvis/jarvis_agent/main.py +78 -34
  13. jarvis/jarvis_agent/memory_manager.py +195 -0
  14. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  15. jarvis/jarvis_agent/prompt_manager.py +82 -0
  16. jarvis/jarvis_agent/prompts.py +46 -9
  17. jarvis/jarvis_agent/protocols.py +4 -1
  18. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  19. jarvis/jarvis_agent/run_loop.py +146 -0
  20. jarvis/jarvis_agent/session_manager.py +9 -9
  21. jarvis/jarvis_agent/share_manager.py +228 -0
  22. jarvis/jarvis_agent/shell_input_handler.py +23 -3
  23. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  24. jarvis/jarvis_agent/task_analyzer.py +212 -0
  25. jarvis/jarvis_agent/task_manager.py +154 -0
  26. jarvis/jarvis_agent/task_planner.py +496 -0
  27. jarvis/jarvis_agent/tool_executor.py +8 -4
  28. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  29. jarvis/jarvis_agent/user_interaction.py +42 -0
  30. jarvis/jarvis_agent/utils.py +54 -0
  31. jarvis/jarvis_agent/web_bridge.py +189 -0
  32. jarvis/jarvis_agent/web_output_sink.py +53 -0
  33. jarvis/jarvis_agent/web_server.py +751 -0
  34. jarvis/jarvis_c2rust/__init__.py +26 -0
  35. jarvis/jarvis_c2rust/cli.py +613 -0
  36. jarvis/jarvis_c2rust/collector.py +258 -0
  37. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  38. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  39. jarvis/jarvis_c2rust/optimizer.py +960 -0
  40. jarvis/jarvis_c2rust/scanner.py +1681 -0
  41. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  42. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  43. jarvis/jarvis_code_agent/code_agent.py +1605 -178
  44. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  45. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  46. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  47. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  48. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  61. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  62. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  63. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  64. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  65. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  66. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  68. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  69. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  70. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  71. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  72. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  73. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  74. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  75. jarvis/jarvis_code_agent/lint.py +275 -13
  76. jarvis/jarvis_code_agent/utils.py +142 -0
  77. jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
  78. jarvis/jarvis_code_analysis/code_review.py +583 -548
  79. jarvis/jarvis_data/config_schema.json +339 -28
  80. jarvis/jarvis_git_squash/main.py +22 -13
  81. jarvis/jarvis_git_utils/git_commiter.py +171 -55
  82. jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
  83. jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
  84. jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
  85. jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
  86. jarvis/jarvis_methodology/main.py +48 -63
  87. jarvis/jarvis_multi_agent/__init__.py +302 -43
  88. jarvis/jarvis_multi_agent/main.py +70 -24
  89. jarvis/jarvis_platform/ai8.py +40 -23
  90. jarvis/jarvis_platform/base.py +210 -49
  91. jarvis/jarvis_platform/human.py +11 -1
  92. jarvis/jarvis_platform/kimi.py +82 -76
  93. jarvis/jarvis_platform/openai.py +73 -1
  94. jarvis/jarvis_platform/registry.py +8 -15
  95. jarvis/jarvis_platform/tongyi.py +115 -101
  96. jarvis/jarvis_platform/yuanbao.py +89 -63
  97. jarvis/jarvis_platform_manager/main.py +194 -132
  98. jarvis/jarvis_platform_manager/service.py +122 -86
  99. jarvis/jarvis_rag/cli.py +156 -53
  100. jarvis/jarvis_rag/embedding_manager.py +155 -12
  101. jarvis/jarvis_rag/llm_interface.py +10 -13
  102. jarvis/jarvis_rag/query_rewriter.py +63 -12
  103. jarvis/jarvis_rag/rag_pipeline.py +222 -40
  104. jarvis/jarvis_rag/reranker.py +26 -3
  105. jarvis/jarvis_rag/retriever.py +270 -14
  106. jarvis/jarvis_sec/__init__.py +3605 -0
  107. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  108. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  109. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  110. jarvis/jarvis_sec/cli.py +116 -0
  111. jarvis/jarvis_sec/report.py +257 -0
  112. jarvis/jarvis_sec/status.py +264 -0
  113. jarvis/jarvis_sec/types.py +20 -0
  114. jarvis/jarvis_sec/workflow.py +219 -0
  115. jarvis/jarvis_smart_shell/main.py +405 -137
  116. jarvis/jarvis_stats/__init__.py +13 -0
  117. jarvis/jarvis_stats/cli.py +387 -0
  118. jarvis/jarvis_stats/stats.py +711 -0
  119. jarvis/jarvis_stats/storage.py +612 -0
  120. jarvis/jarvis_stats/visualizer.py +282 -0
  121. jarvis/jarvis_tools/ask_user.py +1 -0
  122. jarvis/jarvis_tools/base.py +18 -2
  123. jarvis/jarvis_tools/clear_memory.py +239 -0
  124. jarvis/jarvis_tools/cli/main.py +220 -144
  125. jarvis/jarvis_tools/execute_script.py +52 -12
  126. jarvis/jarvis_tools/file_analyzer.py +17 -12
  127. jarvis/jarvis_tools/generate_new_tool.py +46 -24
  128. jarvis/jarvis_tools/read_code.py +277 -18
  129. jarvis/jarvis_tools/read_symbols.py +141 -0
  130. jarvis/jarvis_tools/read_webpage.py +86 -13
  131. jarvis/jarvis_tools/registry.py +294 -90
  132. jarvis/jarvis_tools/retrieve_memory.py +227 -0
  133. jarvis/jarvis_tools/save_memory.py +194 -0
  134. jarvis/jarvis_tools/search_web.py +62 -28
  135. jarvis/jarvis_tools/sub_agent.py +205 -0
  136. jarvis/jarvis_tools/sub_code_agent.py +217 -0
  137. jarvis/jarvis_tools/virtual_tty.py +330 -62
  138. jarvis/jarvis_utils/builtin_replace_map.py +4 -5
  139. jarvis/jarvis_utils/clipboard.py +90 -0
  140. jarvis/jarvis_utils/config.py +607 -50
  141. jarvis/jarvis_utils/embedding.py +3 -0
  142. jarvis/jarvis_utils/fzf.py +57 -0
  143. jarvis/jarvis_utils/git_utils.py +251 -29
  144. jarvis/jarvis_utils/globals.py +174 -17
  145. jarvis/jarvis_utils/http.py +58 -79
  146. jarvis/jarvis_utils/input.py +899 -153
  147. jarvis/jarvis_utils/methodology.py +210 -83
  148. jarvis/jarvis_utils/output.py +220 -137
  149. jarvis/jarvis_utils/utils.py +1906 -135
  150. jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
  151. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  152. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
  153. jarvis/jarvis_git_details/main.py +0 -265
  154. jarvis/jarvis_platform/oyi.py +0 -357
  155. jarvis/jarvis_tools/edit_file.py +0 -255
  156. jarvis/jarvis_tools/rewrite_file.py +0 -195
  157. jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
  158. jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
  159. /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
  160. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  161. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  162. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
- import fcntl
3
2
  import os
4
- import pty
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
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
7
+
8
+ # 为了类型检查,总是导入这些模块
9
+ if TYPE_CHECKING:
10
+ pass
11
+
12
+ # 平台相关的导入
13
+ if sys.platform != "win32":
14
+ pass
15
+ else:
16
+ # Windows平台的导入
17
+ pass
9
18
 
10
19
 
11
20
  class VirtualTTYTool:
@@ -14,6 +23,7 @@ class VirtualTTYTool:
14
23
  "控制虚拟终端执行各种操作,如启动终端、输入命令、获取输出等。"
15
24
  + "与execute_script不同,此工具会创建一个持久的虚拟终端会话,可以连续执行多个命令,并保持终端状态。"
16
25
  + "适用于需要交互式操作的场景,如运行需要用户输入的交互式程序(如:ssh连接、sftp传输、gdb/dlv调试等)。"
26
+ + "注意:Windows平台功能有限,某些Unix特有功能可能不可用。"
17
27
  )
18
28
  parameters = {
19
29
  "type": "object",
@@ -76,11 +86,21 @@ class VirtualTTYTool:
76
86
 
77
87
  # 如果指定的tty_id不存在,为其创建一个新的tty_data
78
88
  if tty_id not in agent.tty_sessions:
79
- agent.tty_sessions[tty_id] = {
80
- "master_fd": None,
81
- "pid": None,
82
- "shell": "/bin/bash",
83
- }
89
+ if sys.platform == "win32":
90
+ import queue as _queue # pylint: disable=import-outside-toplevel
91
+
92
+ agent.tty_sessions[tty_id] = {
93
+ "process": None,
94
+ "output_queue": _queue.Queue(),
95
+ "output_thread": None,
96
+ "shell": "cmd.exe",
97
+ }
98
+ else:
99
+ agent.tty_sessions[tty_id] = {
100
+ "master_fd": None,
101
+ "pid": None,
102
+ "shell": "/bin/bash",
103
+ }
84
104
 
85
105
  action = args.get("action", "").strip().lower()
86
106
 
@@ -96,62 +116,62 @@ class VirtualTTYTool:
96
116
  try:
97
117
  if action == "launch":
98
118
  if args.get("keys", "") != "":
99
- print(f"🚫 启动虚拟终端时,不能同时指定keys参数")
119
+ PrettyOutput.print(
120
+ "启动虚拟终端时,不能同时指定 keys 参数", OutputType.ERROR
121
+ )
100
122
  return {
101
123
  "success": False,
102
124
  "stdout": "",
103
125
  "stderr": "启动虚拟终端时,不能同时指定keys参数",
104
126
  }
105
- print(f"🚀 正在启动虚拟终端 [{tty_id}]...")
127
+
106
128
  result = self._launch_tty(agent, tty_id)
107
- if result["success"]:
108
- print(f"✅ 启动虚拟终端 [{tty_id}] 成功")
109
- else:
110
- print(f"❌ 启动虚拟终端 [{tty_id}] 失败")
129
+ if not result["success"]:
130
+ PrettyOutput.print(
131
+ f"启动虚拟终端 [{tty_id}] 失败", OutputType.ERROR
132
+ )
111
133
  return result
112
134
  elif action == "send_keys":
113
135
  keys = args.get("keys", "").strip()
114
136
  add_enter = args.get("add_enter", True)
115
137
  timeout = args.get("timeout", 5.0) # 默认5秒超时
116
- print(f"⌨️ 正在向终端 [{tty_id}] 发送按键序列: {keys}...")
138
+
117
139
  result = self._input_command(agent, tty_id, keys, timeout, add_enter)
118
- if result["success"]:
119
- print(f"✅ 发送按键序列到终端 [{tty_id}] 成功")
120
- else:
121
- print(f"❌ 发送按键序列到终端 [{tty_id}] 失败")
140
+ if not result["success"]:
141
+ PrettyOutput.print(
142
+ f"发送按键序列到终端 [{tty_id}] 失败", OutputType.ERROR
143
+ )
122
144
  return result
123
145
  elif action == "output":
124
146
  timeout = args.get("timeout", 5.0) # 默认5秒超时
125
- print(f"📥 正在获取终端 [{tty_id}] 输出...")
147
+
126
148
  result = self._get_output(agent, tty_id, timeout)
127
- if result["success"]:
128
- print(f"✅ 获取终端 [{tty_id}] 输出成功")
129
- else:
130
- print(f"❌ 获取终端 [{tty_id}] 输出失败")
149
+ if not result["success"]:
150
+ PrettyOutput.print(
151
+ f"获取终端 [{tty_id}] 输出失败", OutputType.ERROR
152
+ )
131
153
  return result
132
154
  elif action == "close":
133
- print(f"🔒 正在关闭虚拟终端 [{tty_id}]...")
155
+
134
156
  result = self._close_tty(agent, tty_id)
135
- if result["success"]:
136
- print(f"✅ 关闭虚拟终端 [{tty_id}] 成功")
137
- else:
138
- print(f"❌ 关闭虚拟终端 [{tty_id}] 失败")
157
+ if not result["success"]:
158
+ PrettyOutput.print(
159
+ f"关闭虚拟终端 [{tty_id}] 失败", OutputType.ERROR
160
+ )
139
161
  return result
140
162
  elif action == "get_screen":
141
- print(f"🖥️ 正在获取终端 [{tty_id}] 屏幕内容...")
163
+
142
164
  result = self._get_screen(agent, tty_id)
143
- if result["success"]:
144
- print(f"✅ 获取终端 [{tty_id}] 屏幕内容成功")
145
- else:
146
- print(f"❌ 获取终端 [{tty_id}] 屏幕内容失败")
165
+ if not result["success"]:
166
+ PrettyOutput.print(
167
+ f"获取终端 [{tty_id}] 屏幕内容失败", OutputType.ERROR
168
+ )
147
169
  return result
148
170
  elif action == "list":
149
- print("📋 正在获取所有虚拟终端列表...")
171
+
150
172
  result = self._list_ttys(agent)
151
- if result["success"]:
152
- print(" 获取虚拟终端列表成功")
153
- else:
154
- print("❌ 获取虚拟终端列表失败")
173
+ if not result["success"]:
174
+ PrettyOutput.print("获取虚拟终端列表失败", OutputType.ERROR)
155
175
  return result
156
176
  return {"success": False, "stdout": "", "stderr": "不支持的操作"}
157
177
 
@@ -164,13 +184,25 @@ class VirtualTTYTool:
164
184
 
165
185
  def _launch_tty(self, agent: Any, tty_id: str) -> Dict[str, Any]:
166
186
  """启动虚拟终端"""
187
+ if sys.platform == "win32":
188
+ return self._launch_tty_windows(agent, tty_id)
189
+ else:
190
+ return self._launch_tty_unix(agent, tty_id)
191
+
192
+ def _launch_tty_unix(self, agent: Any, tty_id: str) -> Dict[str, Any]:
193
+ """Unix/Linux平台启动虚拟终端"""
167
194
  try:
168
195
  # 如果该ID的终端已经启动,先关闭它
169
196
  if agent.tty_sessions[tty_id]["master_fd"] is not None:
170
197
  self._close_tty(agent, tty_id)
171
198
 
199
+ # 在Unix平台上导入需要的模块
200
+ import pty as _pty # pylint: disable=import-outside-toplevel
201
+ import fcntl as _fcntl # pylint: disable=import-outside-toplevel
202
+ import select as _select # pylint: disable=import-outside-toplevel
203
+
172
204
  # 创建伪终端
173
- pid, master_fd = pty.fork()
205
+ pid, master_fd = _pty.fork()
174
206
 
175
207
  if pid == 0: # 子进程
176
208
  # 执行shell
@@ -180,7 +212,7 @@ class VirtualTTYTool:
180
212
  )
181
213
  else: # 父进程
182
214
  # 设置非阻塞模式
183
- fcntl.fcntl(master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
215
+ _fcntl.fcntl(master_fd, _fcntl.F_SETFL, os.O_NONBLOCK)
184
216
 
185
217
  # 保存终端状态
186
218
  agent.tty_sessions[tty_id]["master_fd"] = master_fd
@@ -191,7 +223,7 @@ class VirtualTTYTool:
191
223
  start_time = time.time()
192
224
  while time.time() - start_time < 2.0: # 最多等待2秒
193
225
  try:
194
- r, _, _ = select.select([master_fd], [], [], 0.1)
226
+ r, _, _ = _select.select([master_fd], [], [], 0.1)
195
227
  if r:
196
228
  data = os.read(master_fd, 1024)
197
229
  if data:
@@ -199,9 +231,6 @@ class VirtualTTYTool:
199
231
  except BlockingIOError:
200
232
  continue
201
233
 
202
- if output:
203
- print(f"📤 终端 [{tty_id}]: {output}")
204
-
205
234
  return {"success": True, "stdout": output, "stderr": ""}
206
235
 
207
236
  except Exception as e:
@@ -211,6 +240,71 @@ class VirtualTTYTool:
211
240
  "stderr": f"启动虚拟终端 [{tty_id}] 失败: {str(e)}",
212
241
  }
213
242
 
243
+ def _launch_tty_windows(self, agent: Any, tty_id: str) -> Dict[str, Any]:
244
+ """Windows平台启动虚拟终端"""
245
+ try:
246
+ # 如果该ID的终端已经启动,先关闭它
247
+ if agent.tty_sessions[tty_id]["process"] is not None:
248
+ self._close_tty(agent, tty_id)
249
+
250
+ # 在Windows平台上导入需要的模块
251
+ import subprocess as _subprocess # pylint: disable=import-outside-toplevel
252
+ import threading as _threading # pylint: disable=import-outside-toplevel
253
+ import queue as _queue # pylint: disable=import-outside-toplevel
254
+
255
+ # 创建子进程
256
+ process = _subprocess.Popen(
257
+ agent.tty_sessions[tty_id]["shell"],
258
+ stdin=_subprocess.PIPE,
259
+ stdout=_subprocess.PIPE,
260
+ stderr=_subprocess.STDOUT,
261
+ shell=True,
262
+ text=True,
263
+ bufsize=0,
264
+ encoding="utf-8",
265
+ errors="replace",
266
+ )
267
+
268
+ # 保存进程对象
269
+ agent.tty_sessions[tty_id]["process"] = process
270
+
271
+ # 创建输出读取线程
272
+ def read_output():
273
+ while True:
274
+ if process is None or process.poll() is not None:
275
+ break
276
+ try:
277
+ if process.stdout is None:
278
+ break
279
+ line = process.stdout.readline()
280
+ if line:
281
+ agent.tty_sessions[tty_id]["output_queue"].put(line)
282
+ except Exception:
283
+ break
284
+
285
+ output_thread = _threading.Thread(target=read_output, daemon=True)
286
+ output_thread.start()
287
+ agent.tty_sessions[tty_id]["output_thread"] = output_thread
288
+
289
+ # 读取初始输出
290
+ output = ""
291
+ start_time = time.time()
292
+ while time.time() - start_time < 2.0: # 最多等待2秒
293
+ try:
294
+ line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
295
+ output += line
296
+ except _queue.Empty:
297
+ continue
298
+
299
+ return {"success": True, "stdout": output, "stderr": ""}
300
+
301
+ except Exception as e:
302
+ return {
303
+ "success": False,
304
+ "stdout": "",
305
+ "stderr": f"启动虚拟终端 [{tty_id}] 失败: {str(e)}",
306
+ }
307
+
214
308
  def _input_command(
215
309
  self,
216
310
  agent: Any,
@@ -225,6 +319,22 @@ class VirtualTTYTool:
225
319
  command: 要输入的单行命令
226
320
  add_enter: 是否在命令末尾添加回车符
227
321
  """
322
+ if sys.platform == "win32":
323
+ return self._input_command_windows(
324
+ agent, tty_id, command, timeout, add_enter
325
+ )
326
+ else:
327
+ return self._input_command_unix(agent, tty_id, command, timeout, add_enter)
328
+
329
+ def _input_command_unix(
330
+ self,
331
+ agent: Any,
332
+ tty_id: str,
333
+ command: str,
334
+ timeout: float,
335
+ add_enter: bool = True,
336
+ ) -> Dict[str, Any]:
337
+ """Unix/Linux平台输入命令"""
228
338
  if agent.tty_sessions[tty_id]["master_fd"] is None:
229
339
  return {
230
340
  "success": False,
@@ -251,7 +361,9 @@ class VirtualTTYTool:
251
361
  while time.time() - start_time < timeout:
252
362
  try:
253
363
  # 使用select等待数据可读
254
- r, _, _ = select.select(
364
+ import select as _select # pylint: disable=import-outside-toplevel
365
+
366
+ r, _, _ = _select.select(
255
367
  [agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
256
368
  )
257
369
  if r:
@@ -260,7 +372,54 @@ class VirtualTTYTool:
260
372
  output += data.decode()
261
373
  except BlockingIOError:
262
374
  continue
263
- print(f"📤 终端 [{tty_id}]: {output}")
375
+ return {"success": True, "stdout": output, "stderr": ""}
376
+
377
+ except Exception as e:
378
+ return {
379
+ "success": False,
380
+ "stdout": "",
381
+ "stderr": f"在终端 [{tty_id}] 执行命令失败: {str(e)}",
382
+ }
383
+
384
+ def _input_command_windows(
385
+ self,
386
+ agent: Any,
387
+ tty_id: str,
388
+ command: str,
389
+ timeout: float,
390
+ add_enter: bool = True,
391
+ ) -> Dict[str, Any]:
392
+ """Windows平台输入命令"""
393
+ if agent.tty_sessions[tty_id]["process"] is None:
394
+ return {
395
+ "success": False,
396
+ "stdout": "",
397
+ "stderr": f"虚拟终端 [{tty_id}] 未启动",
398
+ }
399
+
400
+ # 严格检查并拒绝多行输入
401
+ if "\n" in command:
402
+ return {"success": False, "stdout": "", "stderr": "错误:禁止多行输入"}
403
+
404
+ try:
405
+ # 根据add_enter参数决定是否添加回车符
406
+ if add_enter:
407
+ command = command + "\n"
408
+
409
+ # 发送命令
410
+ agent.tty_sessions[tty_id]["process"].stdin.write(command)
411
+ agent.tty_sessions[tty_id]["process"].stdin.flush()
412
+
413
+ # 等待输出
414
+ output = ""
415
+ start_time = time.time()
416
+ while time.time() - start_time < timeout:
417
+ try:
418
+ line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
419
+ output += line
420
+ except Exception: # queue.Empty
421
+ continue
422
+
264
423
  return {"success": True, "stdout": output, "stderr": ""}
265
424
 
266
425
  except Exception as e:
@@ -274,6 +433,15 @@ class VirtualTTYTool:
274
433
  self, agent: Any, tty_id: str, timeout: float = 5.0
275
434
  ) -> Dict[str, Any]:
276
435
  """获取终端输出"""
436
+ if sys.platform == "win32":
437
+ return self._get_output_windows(agent, tty_id, timeout)
438
+ else:
439
+ return self._get_output_unix(agent, tty_id, timeout)
440
+
441
+ def _get_output_unix(
442
+ self, agent: Any, tty_id: str, timeout: float = 5.0
443
+ ) -> Dict[str, Any]:
444
+ """Unix/Linux平台获取输出"""
277
445
  if agent.tty_sessions[tty_id]["master_fd"] is None:
278
446
  return {
279
447
  "success": False,
@@ -287,7 +455,9 @@ class VirtualTTYTool:
287
455
 
288
456
  while time.time() - start_time < timeout:
289
457
  # 使用select等待数据可读
290
- r, _, _ = select.select(
458
+ import select as _select # pylint: disable=import-outside-toplevel
459
+
460
+ r, _, _ = _select.select(
291
461
  [agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
292
462
  )
293
463
  if r:
@@ -302,7 +472,36 @@ class VirtualTTYTool:
302
472
  break
303
473
  except BlockingIOError:
304
474
  break
305
- print(f"📤 终端 [{tty_id}]: {output}")
475
+ return {"success": True, "stdout": output, "stderr": ""}
476
+
477
+ except Exception as e:
478
+ return {
479
+ "success": False,
480
+ "stdout": "",
481
+ "stderr": f"获取终端 [{tty_id}] 输出失败: {str(e)}",
482
+ }
483
+
484
+ def _get_output_windows(
485
+ self, agent: Any, tty_id: str, timeout: float = 5.0
486
+ ) -> Dict[str, Any]:
487
+ """Windows平台获取输出"""
488
+ if agent.tty_sessions[tty_id]["process"] is None:
489
+ return {
490
+ "success": False,
491
+ "stdout": "",
492
+ "stderr": f"虚拟终端 [{tty_id}] 未启动",
493
+ }
494
+
495
+ try:
496
+ output = ""
497
+ start_time = time.time()
498
+
499
+ while time.time() - start_time < timeout:
500
+ try:
501
+ line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
502
+ output += line
503
+ except Exception: # queue.Empty
504
+ continue
306
505
 
307
506
  return {"success": True, "stdout": output, "stderr": ""}
308
507
 
@@ -315,6 +514,13 @@ class VirtualTTYTool:
315
514
 
316
515
  def _close_tty(self, agent: Any, tty_id: str) -> Dict[str, Any]:
317
516
  """关闭虚拟终端"""
517
+ if sys.platform == "win32":
518
+ return self._close_tty_windows(agent, tty_id)
519
+ else:
520
+ return self._close_tty_unix(agent, tty_id)
521
+
522
+ def _close_tty_unix(self, agent: Any, tty_id: str) -> Dict[str, Any]:
523
+ """Unix/Linux平台关闭终端"""
318
524
  if agent.tty_sessions[tty_id]["master_fd"] is None:
319
525
  return {
320
526
  "success": True,
@@ -328,7 +534,9 @@ class VirtualTTYTool:
328
534
 
329
535
  # 终止子进程
330
536
  if agent.tty_sessions[tty_id]["pid"]:
331
- os.kill(agent.tty_sessions[tty_id]["pid"], signal.SIGTERM)
537
+ import signal as _signal # pylint: disable=import-outside-toplevel
538
+
539
+ os.kill(agent.tty_sessions[tty_id]["pid"], _signal.SIGTERM)
332
540
 
333
541
  # 重置终端数据
334
542
  agent.tty_sessions[tty_id] = {
@@ -350,8 +558,53 @@ class VirtualTTYTool:
350
558
  "stderr": f"关闭虚拟终端 [{tty_id}] 失败: {str(e)}",
351
559
  }
352
560
 
561
+ def _close_tty_windows(self, agent: Any, tty_id: str) -> Dict[str, Any]:
562
+ """Windows平台关闭终端"""
563
+ if agent.tty_sessions[tty_id]["process"] is None:
564
+ return {
565
+ "success": True,
566
+ "stdout": f"没有正在运行的虚拟终端 [{tty_id}]",
567
+ "stderr": "",
568
+ }
569
+
570
+ try:
571
+ # 终止进程
572
+ agent.tty_sessions[tty_id]["process"].terminate()
573
+ agent.tty_sessions[tty_id]["process"].wait()
574
+
575
+ # 重置终端数据
576
+ import queue as _queue # pylint: disable=import-outside-toplevel
577
+
578
+ agent.tty_sessions[tty_id] = {
579
+ "process": None,
580
+ "output_queue": _queue.Queue(),
581
+ "output_thread": None,
582
+ "shell": "cmd.exe",
583
+ }
584
+
585
+ return {
586
+ "success": True,
587
+ "stdout": f"虚拟终端 [{tty_id}] 已关闭",
588
+ "stderr": "",
589
+ }
590
+
591
+ except Exception as e:
592
+ return {
593
+ "success": False,
594
+ "stdout": "",
595
+ "stderr": f"关闭虚拟终端 [{tty_id}] 失败: {str(e)}",
596
+ }
597
+
353
598
  def _get_screen(self, agent: Any, tty_id: str) -> Dict[str, Any]:
354
599
  """获取当前终端屏幕内容"""
600
+ if sys.platform == "win32":
601
+ # Windows平台暂不支持获取屏幕内容
602
+ return {
603
+ "success": False,
604
+ "stdout": "",
605
+ "stderr": "Windows平台暂不支持获取屏幕内容功能",
606
+ }
607
+
355
608
  if agent.tty_sessions[tty_id]["master_fd"] is None:
356
609
  return {
357
610
  "success": False,
@@ -371,7 +624,9 @@ class VirtualTTYTool:
371
624
  start_time = time.time()
372
625
  while time.time() - start_time < 2.0: # 最多等待2秒
373
626
  try:
374
- r, _, _ = select.select(
627
+ import select as _select # pylint: disable=import-outside-toplevel
628
+
629
+ r, _, _ = _select.select(
375
630
  [agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
376
631
  )
377
632
  if r:
@@ -404,15 +659,28 @@ class VirtualTTYTool:
404
659
  active_ttys = []
405
660
 
406
661
  for tty_id, tty_data in agent.tty_sessions.items():
407
- status = "活动" if tty_data["master_fd"] is not None else "关闭"
408
- active_ttys.append(
409
- {
410
- "id": tty_id,
411
- "status": status,
412
- "pid": tty_data["pid"] if tty_data["pid"] else None,
413
- "shell": tty_data["shell"],
414
- }
415
- )
662
+ if sys.platform == "win32":
663
+ status = "活动" if tty_data["process"] is not None else "关闭"
664
+ active_ttys.append(
665
+ {
666
+ "id": tty_id,
667
+ "status": status,
668
+ "pid": (
669
+ tty_data["process"].pid if tty_data["process"] else None
670
+ ),
671
+ "shell": tty_data["shell"],
672
+ }
673
+ )
674
+ else:
675
+ status = "活动" if tty_data["master_fd"] is not None else "关闭"
676
+ active_ttys.append(
677
+ {
678
+ "id": tty_id,
679
+ "status": status,
680
+ "pid": tty_data["pid"] if tty_data["pid"] else None,
681
+ "shell": tty_data["shell"],
682
+ }
683
+ )
416
684
 
417
685
  # 格式化输出
418
686
  output = "虚拟终端列表:\n"
@@ -29,28 +29,27 @@ arguments:
29
29
  """,
30
30
  "description": "网页搜索",
31
31
  },
32
-
33
32
  "FindRelatedFiles": {
34
33
  "append": False,
35
- "template": f"""
34
+ "template": """
36
35
  请使用工具在当前目录下查找与以下功能相关的文件:
37
36
  """,
38
37
  },
39
38
  "Dev": {
40
39
  "append": False,
41
- "template": f"""
40
+ "template": """
42
41
  请调用create_code_agent开发以下需求:
43
42
  """,
44
43
  },
45
44
  "Fix": {
46
45
  "append": False,
47
- "template": f"""
46
+ "template": """
48
47
  请修复以下问题:
49
48
  """,
50
49
  },
51
50
  "Check": {
52
51
  "append": True,
53
- "template": f"""
52
+ "template": """
54
53
  请使用静态检查工具检查当前代码,必须严格遵守工具调用格式。
55
54
 
56
55
  检查要求:
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ import platform
3
+ import subprocess
4
+
5
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
+
7
+
8
+ def copy_to_clipboard(text: str) -> None:
9
+ """将文本复制到剪贴板,支持Windows、macOS和Linux
10
+
11
+ 参数:
12
+ text: 要复制的文本
13
+ """
14
+ PrettyOutput.print("--- 剪贴板内容开始 ---", OutputType.INFO)
15
+ print(text)
16
+ PrettyOutput.print("--- 剪贴板内容结束 ---", OutputType.INFO)
17
+
18
+ system = platform.system()
19
+
20
+ # Windows系统
21
+ if system == "Windows":
22
+ try:
23
+ # 使用Windows的clip命令
24
+ process = subprocess.Popen(
25
+ ["clip"],
26
+ stdin=subprocess.PIPE,
27
+ stdout=subprocess.DEVNULL,
28
+ stderr=subprocess.DEVNULL,
29
+ shell=True,
30
+ )
31
+ if process.stdin:
32
+ process.stdin.write(text.encode("utf-8"))
33
+ process.stdin.close()
34
+ return
35
+ except Exception as e:
36
+ PrettyOutput.print(f"使用Windows clip命令时出错: {e}", OutputType.WARNING)
37
+
38
+ # macOS系统
39
+ elif system == "Darwin":
40
+ try:
41
+ process = subprocess.Popen(
42
+ ["pbcopy"],
43
+ stdin=subprocess.PIPE,
44
+ stdout=subprocess.DEVNULL,
45
+ stderr=subprocess.DEVNULL,
46
+ )
47
+ if process.stdin:
48
+ process.stdin.write(text.encode("utf-8"))
49
+ process.stdin.close()
50
+ return
51
+ except Exception as e:
52
+ PrettyOutput.print(f"使用macOS pbcopy命令时出错: {e}", OutputType.WARNING)
53
+
54
+ # Linux系统
55
+ else:
56
+ # 尝试使用 xsel
57
+ try:
58
+ process = subprocess.Popen(
59
+ ["xsel", "-b", "-i"],
60
+ stdin=subprocess.PIPE,
61
+ stdout=subprocess.DEVNULL,
62
+ stderr=subprocess.DEVNULL,
63
+ )
64
+ if process.stdin:
65
+ process.stdin.write(text.encode("utf-8"))
66
+ process.stdin.close()
67
+ return
68
+ except FileNotFoundError:
69
+ pass # xsel 未安装,继续尝试下一个
70
+ except Exception as e:
71
+ PrettyOutput.print(f"使用xsel时出错: {e}", OutputType.WARNING)
72
+
73
+ # 尝试使用 xclip
74
+ try:
75
+ process = subprocess.Popen(
76
+ ["xclip", "-selection", "clipboard"],
77
+ stdin=subprocess.PIPE,
78
+ stdout=subprocess.DEVNULL,
79
+ stderr=subprocess.DEVNULL,
80
+ )
81
+ if process.stdin:
82
+ process.stdin.write(text.encode("utf-8"))
83
+ process.stdin.close()
84
+ return
85
+ except FileNotFoundError:
86
+ PrettyOutput.print(
87
+ "xsel 和 xclip 均未安装, 无法复制到剪贴板", OutputType.WARNING
88
+ )
89
+ except Exception as e:
90
+ PrettyOutput.print(f"使用xclip时出错: {e}", OutputType.WARNING)