jarvis-ai-assistant 0.3.17__py3-none-any.whl → 0.3.19__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 (55) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +23 -10
  3. jarvis/jarvis_agent/edit_file_handler.py +8 -13
  4. jarvis/jarvis_agent/jarvis.py +13 -3
  5. jarvis/jarvis_agent/memory_manager.py +4 -4
  6. jarvis/jarvis_agent/methodology_share_manager.py +2 -2
  7. jarvis/jarvis_agent/task_analyzer.py +4 -3
  8. jarvis/jarvis_agent/task_manager.py +6 -6
  9. jarvis/jarvis_agent/tool_executor.py +2 -2
  10. jarvis/jarvis_agent/tool_share_manager.py +2 -2
  11. jarvis/jarvis_code_agent/code_agent.py +21 -29
  12. jarvis/jarvis_code_analysis/code_review.py +2 -4
  13. jarvis/jarvis_data/config_schema.json +5 -0
  14. jarvis/jarvis_git_utils/git_commiter.py +17 -18
  15. jarvis/jarvis_methodology/main.py +12 -12
  16. jarvis/jarvis_platform/base.py +21 -13
  17. jarvis/jarvis_platform/kimi.py +13 -13
  18. jarvis/jarvis_platform/tongyi.py +17 -15
  19. jarvis/jarvis_platform/yuanbao.py +11 -11
  20. jarvis/jarvis_platform_manager/main.py +12 -22
  21. jarvis/jarvis_rag/cli.py +36 -32
  22. jarvis/jarvis_rag/embedding_manager.py +11 -6
  23. jarvis/jarvis_rag/llm_interface.py +6 -5
  24. jarvis/jarvis_rag/rag_pipeline.py +9 -8
  25. jarvis/jarvis_rag/reranker.py +3 -2
  26. jarvis/jarvis_rag/retriever.py +18 -8
  27. jarvis/jarvis_smart_shell/main.py +306 -46
  28. jarvis/jarvis_stats/stats.py +40 -0
  29. jarvis/jarvis_stats/storage.py +220 -9
  30. jarvis/jarvis_tools/clear_memory.py +0 -11
  31. jarvis/jarvis_tools/cli/main.py +18 -17
  32. jarvis/jarvis_tools/edit_file.py +4 -4
  33. jarvis/jarvis_tools/execute_script.py +5 -1
  34. jarvis/jarvis_tools/file_analyzer.py +6 -6
  35. jarvis/jarvis_tools/generate_new_tool.py +6 -17
  36. jarvis/jarvis_tools/read_code.py +3 -6
  37. jarvis/jarvis_tools/read_webpage.py +74 -13
  38. jarvis/jarvis_tools/registry.py +8 -28
  39. jarvis/jarvis_tools/retrieve_memory.py +5 -16
  40. jarvis/jarvis_tools/rewrite_file.py +0 -4
  41. jarvis/jarvis_tools/save_memory.py +2 -10
  42. jarvis/jarvis_tools/search_web.py +5 -8
  43. jarvis/jarvis_tools/virtual_tty.py +22 -40
  44. jarvis/jarvis_utils/clipboard.py +3 -3
  45. jarvis/jarvis_utils/config.py +8 -0
  46. jarvis/jarvis_utils/input.py +67 -27
  47. jarvis/jarvis_utils/methodology.py +3 -3
  48. jarvis/jarvis_utils/output.py +1 -7
  49. jarvis/jarvis_utils/utils.py +44 -58
  50. {jarvis_ai_assistant-0.3.17.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/METADATA +1 -1
  51. {jarvis_ai_assistant-0.3.17.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/RECORD +55 -55
  52. {jarvis_ai_assistant-0.3.17.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/WHEEL +0 -0
  53. {jarvis_ai_assistant-0.3.17.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/entry_points.txt +0 -0
  54. {jarvis_ai_assistant-0.3.17.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/licenses/LICENSE +0 -0
  55. {jarvis_ai_assistant-0.3.17.dist-info → jarvis_ai_assistant-0.3.19.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,13 @@ from typing import Any, Dict
3
3
 
4
4
  from jarvis.jarvis_platform.registry import PlatformRegistry
5
5
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
+ from jarvis.jarvis_utils.config import (
7
+ get_web_search_platform_name,
8
+ get_web_search_model_name,
9
+ )
10
+ from jarvis.jarvis_utils.http import get as http_get
11
+ from markdownify import markdownify as md # type: ignore
12
+ import requests
6
13
 
7
14
 
8
15
  class WebpageTool:
@@ -22,28 +29,82 @@ class WebpageTool:
22
29
  }
23
30
 
24
31
  def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
25
- """Read webpage content using Yuanbao model"""
32
+ """
33
+ 读取网页内容。
34
+ 优先使用配置的 web_search_platform 与模型的原生web能力;若不支持,则使用requests抓取页面并调用模型进行分析。
35
+ """
26
36
  try:
27
- url = args["url"].strip()
28
- want = args.get("want", "请总结这个网页的主要内容")
37
+ url = str(args.get("url", "")).strip()
38
+ want = str(args.get("want", "请总结这个网页的主要内容"))
29
39
 
30
- # Create Yuanbao model instance
40
+ if not url:
41
+ return {"success": False, "stdout": "", "stderr": "缺少必需参数:url"}
42
+
43
+ # 1) 优先使用配置的 Web 搜索平台与模型(若支持web)
44
+ web_search_platform = get_web_search_platform_name()
45
+ web_search_model = get_web_search_model_name()
46
+ if web_search_platform and web_search_model:
47
+ model = PlatformRegistry().create_platform(web_search_platform)
48
+ if model:
49
+ model.set_model_name(web_search_model)
50
+ if model.support_web():
51
+
52
+ model.set_web(True)
53
+ model.set_suppress_output(False) # type: ignore
54
+ prompt = f"""请帮我处理这个网页:{url}
55
+ 用户的具体需求是:{want}
56
+ 请按照以下要求输出结果:
57
+ 1. 使用Markdown格式
58
+ 2. 包含网页标题
59
+ 3. 根据用户需求提供准确、完整的信息"""
60
+ response = model.chat_until_success(prompt) # type: ignore
61
+ return {"success": True, "stdout": response, "stderr": ""}
62
+
63
+ # 2) 然后尝试使用默认平台(normal)的 web 能力
31
64
  model = PlatformRegistry().get_normal_platform()
32
- model.set_web(True)
33
- model.set_suppress_output(False) # type: ignore
65
+ if model.support_web():
34
66
 
35
- # Construct prompt based on want parameter
36
- prompt = f"""请帮我处理这个网页:{url}
67
+ model.set_web(True)
68
+ model.set_suppress_output(False) # type: ignore
69
+ prompt = f"""请帮我处理这个网页:{url}
37
70
  用户的具体需求是:{want}
38
71
  请按照以下要求输出结果:
39
72
  1. 使用Markdown格式
40
73
  2. 包含网页标题
41
74
  3. 根据用户需求提供准确、完整的信息"""
75
+ response = model.chat_until_success(prompt) # type: ignore
76
+ return {"success": True, "stdout": response, "stderr": ""}
77
+
78
+ # 3) 回退:使用 requests 抓取网页,再用模型分析
79
+
80
+ try:
81
+ resp = http_get(url, timeout=10.0, allow_redirects=True)
82
+ content_md = md(resp.text, strip=["script", "style"])
83
+ except requests.exceptions.HTTPError as e:
84
+ PrettyOutput.print(f"⚠️ HTTP错误 {e.response.status_code} 访问 {url}", OutputType.WARNING)
85
+ return {"success": False, "stdout": "", "stderr": f"HTTP错误:{e.response.status_code}"}
86
+ except requests.exceptions.RequestException as e:
87
+ PrettyOutput.print(f"⚠️ 请求错误: {e}", OutputType.WARNING)
88
+ return {"success": False, "stdout": "", "stderr": f"请求错误:{e}"}
89
+
90
+ if not content_md or not content_md.strip():
91
+ return {"success": False, "stdout": "", "stderr": "无法从网页抓取有效内容。"}
42
92
 
43
- # Get response from Yuanbao model
44
- response = model.chat_until_success(prompt) # type: ignore
45
93
 
46
- return {"success": True, "stdout": response, "stderr": ""}
94
+ summary_prompt = f"""以下是网页 {url} 的内容(已转换为Markdown):
95
+ ----------------
96
+ {content_md}
97
+ ----------------
98
+ 请根据用户的具体需求“{want}”进行总结与回答:
99
+ - 使用Markdown格式
100
+ - 包含网页标题(若可推断)
101
+ - 提供准确、完整的信息"""
102
+
103
+ model = PlatformRegistry().get_normal_platform()
104
+ model.set_suppress_output(False) # type: ignore
105
+ summary = model.chat_until_success(summary_prompt) # type: ignore
106
+
107
+ return {"success": True, "stdout": summary, "stderr": ""}
47
108
 
48
109
  except Exception as e:
49
110
  PrettyOutput.print(f"读取网页失败: {str(e)}", OutputType.ERROR)
@@ -55,5 +116,5 @@ class WebpageTool:
55
116
 
56
117
  @staticmethod
57
118
  def check() -> bool:
58
- """检查当前平台是否支持web功能"""
59
- return PlatformRegistry().get_normal_platform().support_web()
119
+ """工具可用性检查:始终可用;若模型不支持web将回退到requests抓取。"""
120
+ return True
@@ -287,10 +287,7 @@ class ToolRegistry(OutputHandlerProtocol):
287
287
  for tool_name in dont_use_list:
288
288
  if tool_name in self.tools:
289
289
  del self.tools[tool_name]
290
- PrettyOutput.print(
291
- f"已排除工具: {tool_name}",
292
- OutputType.INFO,
293
- )
290
+
294
291
 
295
292
  def _load_mcp_tools(self) -> None:
296
293
  """加载MCP工具,优先从配置获取,其次从目录扫描"""
@@ -354,9 +351,7 @@ class ToolRegistry(OutputHandlerProtocol):
354
351
  try:
355
352
  import subprocess
356
353
 
357
- PrettyOutput.print(
358
- f"正在克隆中心工具仓库: {central_repo}", OutputType.INFO
359
- )
354
+
360
355
  subprocess.run(
361
356
  ["git", "clone", central_repo, central_repo_path], check=True
362
357
  )
@@ -399,10 +394,7 @@ class ToolRegistry(OutputHandlerProtocol):
399
394
 
400
395
  # 检查enable标志
401
396
  if not config.get("enable", True):
402
- PrettyOutput.print(
403
- f"MCP配置{config.get('name', '')}已禁用(enable=false),跳过注册",
404
- OutputType.INFO,
405
- )
397
+
406
398
  return False
407
399
 
408
400
  name = config.get("name", "mcp")
@@ -414,10 +406,7 @@ class ToolRegistry(OutputHandlerProtocol):
414
406
  args.pop("agent", None)
415
407
  args.pop("want", None)
416
408
  ret = client.get_resource_list()
417
- PrettyOutput.print(
418
- f"MCP {name} 资源列表:\n{yaml.safe_dump(ret, allow_unicode=True)}",
419
- OutputType.TOOL,
420
- )
409
+
421
410
  return {
422
411
  "success": True,
423
412
  "stdout": yaml.safe_dump(ret, allow_unicode=True),
@@ -438,10 +427,7 @@ class ToolRegistry(OutputHandlerProtocol):
438
427
  "stderr": "缺少必需的uri参数",
439
428
  }
440
429
  ret = client.get_resource(args["uri"])
441
- PrettyOutput.print(
442
- f"MCP {name} 获取资源:\n{yaml.safe_dump(ret, allow_unicode=True)}",
443
- OutputType.TOOL,
444
- )
430
+
445
431
  return ret
446
432
 
447
433
  return execute
@@ -452,10 +438,7 @@ class ToolRegistry(OutputHandlerProtocol):
452
438
  args.pop("agent", None)
453
439
  args.pop("want", None)
454
440
  ret = client.execute(tool_name, args)
455
- PrettyOutput.print(
456
- f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret, allow_unicode=True)}",
457
- OutputType.TOOL,
458
- )
441
+
459
442
  return ret
460
443
 
461
444
  return execute
@@ -665,11 +648,8 @@ class ToolRegistry(OutputHandlerProtocol):
665
648
  yaml.safe_load(temp_data[0]) # Check if valid YAML
666
649
 
667
650
  # Ask user for confirmation
668
- PrettyOutput.print(
669
- f"检测到缺失的 {ct('TOOL_CALL')} 标签,已自动修复。修复后的内容如下:",
670
- OutputType.INFO,
671
- )
672
- PrettyOutput.print(fixed_content, OutputType.TOOL)
651
+
652
+
673
653
  data = temp_data
674
654
  except (yaml.YAMLError, EOFError, KeyboardInterrupt):
675
655
  # Even after fixing, it's not valid YAML, or user cancelled.
@@ -147,17 +147,12 @@ class RetrieveMemoryTool:
147
147
 
148
148
  # 检查是否超过token限制
149
149
  if total_tokens + memory_tokens > memory_token_limit:
150
- PrettyOutput.print(
151
- f"达到token限制 ({total_tokens}/{memory_token_limit}),停止加载更多记忆",
152
- OutputType.INFO,
153
- )
150
+
154
151
  break
155
152
 
156
153
  # 检查是否超过50条限制
157
154
  if len(filtered_memories) >= 50:
158
- PrettyOutput.print(
159
- f"达到记忆条数限制 (50条),停止加载更多记忆", OutputType.INFO
160
- )
155
+
161
156
  break
162
157
 
163
158
  filtered_memories.append(memory)
@@ -170,10 +165,10 @@ class RetrieveMemoryTool:
170
165
  all_memories = all_memories[:limit]
171
166
 
172
167
  # 打印结果摘要
173
- PrettyOutput.print(f"检索到 {len(all_memories)} 条记忆", OutputType.INFO)
168
+
174
169
 
175
170
  if tags:
176
- PrettyOutput.print(f"使用标签过滤: {', '.join(tags)}", OutputType.INFO)
171
+ pass
177
172
 
178
173
  # 格式化为Markdown输出
179
174
  markdown_output = f"# 记忆检索结果\n\n"
@@ -216,17 +211,11 @@ class RetrieveMemoryTool:
216
211
 
217
212
  # 如果记忆较多,在终端显示摘要
218
213
  if len(all_memories) > 5:
219
- PrettyOutput.print(f"记忆较多,仅显示前5条摘要:", OutputType.INFO)
214
+ # 静默模式下不再打印摘要,完整结果已包含在返回的markdown_output中
220
215
  for i, memory in enumerate(all_memories[:5]):
221
216
  content_preview = memory.get("content", "")[:100]
222
217
  if len(memory.get("content", "")) > 100:
223
218
  content_preview += "..."
224
- PrettyOutput.print(
225
- f"{i+1}. [{memory.get('type')}] {memory.get('id')}\n"
226
- f" 标签: {', '.join(memory.get('tags', []))}\n"
227
- f" 内容: {content_preview}",
228
- OutputType.INFO,
229
- )
230
219
 
231
220
  return {
232
221
  "success": True,
@@ -114,7 +114,6 @@ class FileRewriteTool:
114
114
  action = "创建并写入" if not file_exists else "成功重写"
115
115
  stdout_message = f"文件 {abs_path} {action}"
116
116
  stdout_messages.append(stdout_message)
117
- PrettyOutput.print(stdout_message, OutputType.SUCCESS)
118
117
 
119
118
  except Exception as e:
120
119
  stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
@@ -126,7 +125,6 @@ class FileRewriteTool:
126
125
  if not success and processed:
127
126
  rollback_message = "操作失败,正在回滚修改..."
128
127
  stderr_messages.append(rollback_message)
129
- PrettyOutput.print(rollback_message, OutputType.WARNING)
130
128
 
131
129
  try:
132
130
  if original_content is None:
@@ -141,7 +139,6 @@ class FileRewriteTool:
141
139
  rollback_file_message = f"已回滚文件: {abs_path}"
142
140
 
143
141
  stderr_messages.append(rollback_file_message)
144
- PrettyOutput.print(rollback_file_message, OutputType.INFO)
145
142
  except Exception as e:
146
143
  rollback_error = f"回滚文件 {file_path} 失败: {str(e)}"
147
144
  stderr_messages.append(rollback_error)
@@ -172,7 +169,6 @@ class FileRewriteTool:
172
169
  if processed:
173
170
  rollback_message = "操作失败,正在回滚修改..."
174
171
  stderr_messages.append(rollback_message)
175
- PrettyOutput.print(rollback_message, OutputType.WARNING)
176
172
 
177
173
  try:
178
174
  if original_content is None:
@@ -159,12 +159,7 @@ class SaveMemoryTool:
159
159
  # 打印单条记忆保存信息
160
160
  memory_type = memory_data["memory_type"]
161
161
  tags = memory_data.get("tags", [])
162
- PrettyOutput.print(
163
- f"[{i+1}/{len(memories)}] {memory_type} 记忆已保存\n"
164
- f"ID: {result['memory_id']}\n"
165
- f"标签: {', '.join(tags)}",
166
- OutputType.SUCCESS,
167
- )
162
+
168
163
  except Exception as e:
169
164
  failed_count += 1
170
165
  error_msg = f"保存第 {i+1} 条记忆失败: {str(e)}"
@@ -178,10 +173,7 @@ class SaveMemoryTool:
178
173
  )
179
174
 
180
175
  # 生成总结报告
181
- PrettyOutput.print(
182
- f"\n批量保存完成:成功 {success_count} 条,失败 {failed_count} 条",
183
- OutputType.INFO,
184
- )
176
+
185
177
 
186
178
  # 构建返回结果
187
179
  output = {
@@ -34,7 +34,7 @@ class SearchWebTool:
34
34
  # pylint: disable=too-many-locals, broad-except
35
35
  """执行网络搜索、抓取内容并总结结果。"""
36
36
  try:
37
- PrettyOutput.print("▶️ 使用 DuckDuckGo 开始网页搜索...", OutputType.INFO)
37
+
38
38
  results = list(DDGS().text(query, max_results=50, page=3))
39
39
 
40
40
  if not results:
@@ -50,17 +50,14 @@ class SearchWebTool:
50
50
 
51
51
  for r in results:
52
52
  if visited_count >= 10:
53
- PrettyOutput.print("ℹ️ 已成功获取10个网页,停止抓取。", OutputType.INFO)
53
+
54
54
  break
55
55
 
56
56
  url = r["href"]
57
57
  title = r.get("title", url)
58
58
 
59
59
  try:
60
- PrettyOutput.print(
61
- f"📄 ({visited_count + 1}/10) 正在抓取: {title} ({url})",
62
- OutputType.INFO,
63
- )
60
+
64
61
  response = http_get(url, timeout=10.0, allow_redirects=True)
65
62
  content = md(response.text, strip=["script", "style"])
66
63
  if content:
@@ -83,9 +80,9 @@ class SearchWebTool:
83
80
  }
84
81
 
85
82
  url_list_str = "\n".join(f" - {u}" for u in visited_urls)
86
- PrettyOutput.print(f"🔍 已成功访问并处理以下URL:\n{url_list_str}", OutputType.INFO)
87
83
 
88
- PrettyOutput.print("🧠 正在总结内容...", OutputType.INFO)
84
+
85
+
89
86
  summary_prompt = f"请为查询“{query}”总结以下内容:\n\n{full_content}"
90
87
 
91
88
  if not agent.model:
@@ -3,6 +3,7 @@ import os
3
3
  import sys
4
4
  import time
5
5
  from typing import Any, Dict, TYPE_CHECKING
6
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
7
 
7
8
  # 为了类型检查,总是导入这些模块
8
9
  if TYPE_CHECKING:
@@ -126,62 +127,50 @@ class VirtualTTYTool:
126
127
  try:
127
128
  if action == "launch":
128
129
  if args.get("keys", "") != "":
129
- print(f"🚫 启动虚拟终端时,不能同时指定keys参数")
130
+ PrettyOutput.print("启动虚拟终端时,不能同时指定 keys 参数", OutputType.ERROR)
130
131
  return {
131
132
  "success": False,
132
133
  "stdout": "",
133
134
  "stderr": "启动虚拟终端时,不能同时指定keys参数",
134
135
  }
135
- print(f"🚀 正在启动虚拟终端 [{tty_id}]...")
136
+
136
137
  result = self._launch_tty(agent, tty_id)
137
- if result["success"]:
138
- print(f"启动虚拟终端 [{tty_id}] 成功")
139
- else:
140
- print(f"❌ 启动虚拟终端 [{tty_id}] 失败")
138
+ if not result["success"]:
139
+ PrettyOutput.print(f"启动虚拟终端 [{tty_id}] 失败", OutputType.ERROR)
141
140
  return result
142
141
  elif action == "send_keys":
143
142
  keys = args.get("keys", "").strip()
144
143
  add_enter = args.get("add_enter", True)
145
144
  timeout = args.get("timeout", 5.0) # 默认5秒超时
146
- print(f"⌨️ 正在向终端 [{tty_id}] 发送按键序列: {keys}...")
145
+
147
146
  result = self._input_command(agent, tty_id, keys, timeout, add_enter)
148
- if result["success"]:
149
- print(f"发送按键序列到终端 [{tty_id}] 成功")
150
- else:
151
- print(f"❌ 发送按键序列到终端 [{tty_id}] 失败")
147
+ if not result["success"]:
148
+ PrettyOutput.print(f"发送按键序列到终端 [{tty_id}] 失败", OutputType.ERROR)
152
149
  return result
153
150
  elif action == "output":
154
151
  timeout = args.get("timeout", 5.0) # 默认5秒超时
155
- print(f"📥 正在获取终端 [{tty_id}] 输出...")
152
+
156
153
  result = self._get_output(agent, tty_id, timeout)
157
- if result["success"]:
158
- print(f"获取终端 [{tty_id}] 输出成功")
159
- else:
160
- print(f"❌ 获取终端 [{tty_id}] 输出失败")
154
+ if not result["success"]:
155
+ PrettyOutput.print(f"获取终端 [{tty_id}] 输出失败", OutputType.ERROR)
161
156
  return result
162
157
  elif action == "close":
163
- print(f"🔒 正在关闭虚拟终端 [{tty_id}]...")
158
+
164
159
  result = self._close_tty(agent, tty_id)
165
- if result["success"]:
166
- print(f"关闭虚拟终端 [{tty_id}] 成功")
167
- else:
168
- print(f"❌ 关闭虚拟终端 [{tty_id}] 失败")
160
+ if not result["success"]:
161
+ PrettyOutput.print(f"关闭虚拟终端 [{tty_id}] 失败", OutputType.ERROR)
169
162
  return result
170
163
  elif action == "get_screen":
171
- print(f"🖥️ 正在获取终端 [{tty_id}] 屏幕内容...")
164
+
172
165
  result = self._get_screen(agent, tty_id)
173
- if result["success"]:
174
- print(f"获取终端 [{tty_id}] 屏幕内容成功")
175
- else:
176
- print(f"❌ 获取终端 [{tty_id}] 屏幕内容失败")
166
+ if not result["success"]:
167
+ PrettyOutput.print(f"获取终端 [{tty_id}] 屏幕内容失败", OutputType.ERROR)
177
168
  return result
178
169
  elif action == "list":
179
- print("📋 正在获取所有虚拟终端列表...")
170
+
180
171
  result = self._list_ttys(agent)
181
- if result["success"]:
182
- print(" 获取虚拟终端列表成功")
183
- else:
184
- print("❌ 获取虚拟终端列表失败")
172
+ if not result["success"]:
173
+ PrettyOutput.print("获取虚拟终端列表失败", OutputType.ERROR)
185
174
  return result
186
175
  return {"success": False, "stdout": "", "stderr": "不支持的操作"}
187
176
 
@@ -241,8 +230,7 @@ class VirtualTTYTool:
241
230
  except BlockingIOError:
242
231
  continue
243
232
 
244
- if output:
245
- print(f"📤 终端 [{tty_id}]: {output}")
233
+
246
234
 
247
235
  return {"success": True, "stdout": output, "stderr": ""}
248
236
 
@@ -309,8 +297,7 @@ class VirtualTTYTool:
309
297
  except _queue.Empty:
310
298
  continue
311
299
 
312
- if output:
313
- print(f"📤 终端 [{tty_id}]: {output}")
300
+
314
301
 
315
302
  return {"success": True, "stdout": output, "stderr": ""}
316
303
 
@@ -388,7 +375,6 @@ class VirtualTTYTool:
388
375
  output += data.decode()
389
376
  except BlockingIOError:
390
377
  continue
391
- print(f"📤 终端 [{tty_id}]: {output}")
392
378
  return {"success": True, "stdout": output, "stderr": ""}
393
379
 
394
380
  except Exception as e:
@@ -437,7 +423,6 @@ class VirtualTTYTool:
437
423
  except Exception: # queue.Empty
438
424
  continue
439
425
 
440
- print(f"📤 终端 [{tty_id}]: {output}")
441
426
  return {"success": True, "stdout": output, "stderr": ""}
442
427
 
443
428
  except Exception as e:
@@ -490,8 +475,6 @@ class VirtualTTYTool:
490
475
  break
491
476
  except BlockingIOError:
492
477
  break
493
- print(f"📤 终端 [{tty_id}]: {output}")
494
-
495
478
  return {"success": True, "stdout": output, "stderr": ""}
496
479
 
497
480
  except Exception as e:
@@ -523,7 +506,6 @@ class VirtualTTYTool:
523
506
  except Exception: # queue.Empty
524
507
  continue
525
508
 
526
- print(f"📤 终端 [{tty_id}]: {output}")
527
509
  return {"success": True, "stdout": output, "stderr": ""}
528
510
 
529
511
  except Exception as e:
@@ -11,9 +11,9 @@ def copy_to_clipboard(text: str) -> None:
11
11
  参数:
12
12
  text: 要复制的文本
13
13
  """
14
- print("--- 剪贴板内容开始 ---")
15
- print(text)
16
- print("--- 剪贴板内容结束 ---")
14
+ PrettyOutput.print("--- 剪贴板内容开始 ---", OutputType.INFO)
15
+ PrettyOutput.print(text, OutputType.CODE, lang="text")
16
+ PrettyOutput.print("--- 剪贴板内容结束 ---", OutputType.INFO)
17
17
 
18
18
  system = platform.system()
19
19
 
@@ -624,3 +624,11 @@ def is_enable_builtin_config_selector() -> bool:
624
624
  return (
625
625
  GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR", False) is True
626
626
  )
627
+
628
+
629
+ def is_immediate_abort() -> bool:
630
+ """
631
+ 是否启用立即中断:当在对话过程中检测到用户中断信号时,立即停止输出并返回。
632
+ 默认关闭
633
+ """
634
+ return GLOBAL_CONFIG_DATA.get("JARVIS_IMMEDIATE_ABORT", False) is True