jarvis-ai-assistant 0.7.0__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 (159) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +243 -139
  3. jarvis/jarvis_agent/agent_manager.py +5 -10
  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 +265 -15
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +113 -98
  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 +6 -12
  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 +77 -14
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +12 -21
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/task_analyzer.py +26 -4
  31. jarvis/jarvis_agent/task_manager.py +11 -27
  32. jarvis/jarvis_agent/tool_executor.py +2 -3
  33. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  34. jarvis/jarvis_agent/web_server.py +55 -20
  35. jarvis/jarvis_c2rust/__init__.py +5 -5
  36. jarvis/jarvis_c2rust/cli.py +461 -499
  37. jarvis/jarvis_c2rust/collector.py +45 -53
  38. jarvis/jarvis_c2rust/constants.py +26 -0
  39. jarvis/jarvis_c2rust/library_replacer.py +264 -132
  40. jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
  41. jarvis/jarvis_c2rust/loaders.py +207 -0
  42. jarvis/jarvis_c2rust/models.py +28 -0
  43. jarvis/jarvis_c2rust/optimizer.py +1592 -395
  44. jarvis/jarvis_c2rust/transpiler.py +1722 -1064
  45. jarvis/jarvis_c2rust/utils.py +385 -0
  46. jarvis/jarvis_code_agent/build_validation_config.py +2 -3
  47. jarvis/jarvis_code_agent/code_agent.py +394 -320
  48. jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
  61. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
  62. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
  63. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
  64. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
  65. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  66. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
  68. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
  69. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  70. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
  71. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  72. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
  73. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
  74. jarvis/jarvis_code_agent/lint.py +258 -27
  75. jarvis/jarvis_code_agent/utils.py +0 -1
  76. jarvis/jarvis_code_analysis/code_review.py +19 -24
  77. jarvis/jarvis_data/config_schema.json +53 -26
  78. jarvis/jarvis_git_squash/main.py +4 -5
  79. jarvis/jarvis_git_utils/git_commiter.py +44 -49
  80. jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
  81. jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
  82. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  83. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  84. jarvis/jarvis_methodology/main.py +32 -48
  85. jarvis/jarvis_multi_agent/__init__.py +79 -61
  86. jarvis/jarvis_multi_agent/main.py +3 -7
  87. jarvis/jarvis_platform/base.py +469 -199
  88. jarvis/jarvis_platform/human.py +7 -8
  89. jarvis/jarvis_platform/kimi.py +30 -36
  90. jarvis/jarvis_platform/openai.py +65 -27
  91. jarvis/jarvis_platform/registry.py +26 -10
  92. jarvis/jarvis_platform/tongyi.py +24 -25
  93. jarvis/jarvis_platform/yuanbao.py +31 -42
  94. jarvis/jarvis_platform_manager/main.py +66 -77
  95. jarvis/jarvis_platform_manager/service.py +8 -13
  96. jarvis/jarvis_rag/cli.py +49 -51
  97. jarvis/jarvis_rag/embedding_manager.py +13 -18
  98. jarvis/jarvis_rag/llm_interface.py +8 -9
  99. jarvis/jarvis_rag/query_rewriter.py +10 -21
  100. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  101. jarvis/jarvis_rag/reranker.py +4 -5
  102. jarvis/jarvis_rag/retriever.py +28 -30
  103. jarvis/jarvis_sec/__init__.py +220 -3520
  104. jarvis/jarvis_sec/agents.py +143 -0
  105. jarvis/jarvis_sec/analysis.py +276 -0
  106. jarvis/jarvis_sec/cli.py +29 -6
  107. jarvis/jarvis_sec/clustering.py +1439 -0
  108. jarvis/jarvis_sec/file_manager.py +427 -0
  109. jarvis/jarvis_sec/parsers.py +73 -0
  110. jarvis/jarvis_sec/prompts.py +268 -0
  111. jarvis/jarvis_sec/report.py +83 -4
  112. jarvis/jarvis_sec/review.py +453 -0
  113. jarvis/jarvis_sec/utils.py +499 -0
  114. jarvis/jarvis_sec/verification.py +848 -0
  115. jarvis/jarvis_sec/workflow.py +7 -0
  116. jarvis/jarvis_smart_shell/main.py +38 -87
  117. jarvis/jarvis_stats/cli.py +1 -1
  118. jarvis/jarvis_stats/stats.py +7 -7
  119. jarvis/jarvis_stats/storage.py +15 -21
  120. jarvis/jarvis_tools/clear_memory.py +3 -20
  121. jarvis/jarvis_tools/cli/main.py +20 -23
  122. jarvis/jarvis_tools/edit_file.py +1066 -0
  123. jarvis/jarvis_tools/execute_script.py +42 -21
  124. jarvis/jarvis_tools/file_analyzer.py +6 -9
  125. jarvis/jarvis_tools/generate_new_tool.py +11 -20
  126. jarvis/jarvis_tools/lsp_client.py +1552 -0
  127. jarvis/jarvis_tools/methodology.py +2 -3
  128. jarvis/jarvis_tools/read_code.py +1525 -87
  129. jarvis/jarvis_tools/read_symbols.py +2 -3
  130. jarvis/jarvis_tools/read_webpage.py +7 -10
  131. jarvis/jarvis_tools/registry.py +370 -181
  132. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  133. jarvis/jarvis_tools/rewrite_file.py +105 -0
  134. jarvis/jarvis_tools/save_memory.py +3 -15
  135. jarvis/jarvis_tools/search_web.py +3 -7
  136. jarvis/jarvis_tools/sub_agent.py +17 -6
  137. jarvis/jarvis_tools/sub_code_agent.py +14 -16
  138. jarvis/jarvis_tools/virtual_tty.py +54 -32
  139. jarvis/jarvis_utils/clipboard.py +7 -10
  140. jarvis/jarvis_utils/config.py +98 -63
  141. jarvis/jarvis_utils/embedding.py +5 -5
  142. jarvis/jarvis_utils/fzf.py +8 -8
  143. jarvis/jarvis_utils/git_utils.py +81 -67
  144. jarvis/jarvis_utils/input.py +24 -49
  145. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  146. jarvis/jarvis_utils/methodology.py +33 -35
  147. jarvis/jarvis_utils/utils.py +245 -202
  148. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  150. jarvis/jarvis_agent/edit_file_handler.py +0 -584
  151. jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
  152. jarvis/jarvis_agent/task_planner.py +0 -496
  153. jarvis/jarvis_platform/ai8.py +0 -332
  154. jarvis/jarvis_tools/ask_user.py +0 -54
  155. jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
  156. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -8,6 +8,7 @@ import sys
8
8
  import time
9
9
  import atexit
10
10
  import errno
11
+ import threading
11
12
  from pathlib import Path
12
13
  from typing import Any, Callable, Dict, List, Optional, Tuple
13
14
  from datetime import datetime, date
@@ -25,7 +26,6 @@ from jarvis.jarvis_utils.config import (
25
26
  from jarvis.jarvis_utils.embedding import get_context_token_count
26
27
  from jarvis.jarvis_utils.globals import get_in_chat, get_interrupt, set_interrupt
27
28
  from jarvis.jarvis_utils.input import user_confirm
28
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
29
29
 
30
30
  # 向后兼容:导出 get_yes_no 供外部模块引用
31
31
  get_yes_no = user_confirm
@@ -284,20 +284,13 @@ def _acquire_single_instance_lock(lock_name: str = "instance.lock") -> None:
284
284
  if lock_path.exists():
285
285
  pid = _read_lock_owner_pid(lock_path)
286
286
  if pid and _is_process_alive(pid):
287
- PrettyOutput.print(
288
- f"检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。\n"
289
- f"如果确认不存在正在运行的实例,请删除锁文件后重试:{lock_path}",
290
- OutputType.WARNING,
291
- )
287
+ print(f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。\n如果确认不存在正在运行的实例,请删除锁文件后重试:{lock_path}")
292
288
  sys.exit(0)
293
289
  # 尝试移除陈旧锁
294
290
  try:
295
291
  lock_path.unlink()
296
292
  except Exception:
297
- PrettyOutput.print(
298
- f"无法删除旧锁文件:{lock_path},请手动清理后重试。",
299
- OutputType.ERROR,
300
- )
293
+ print(f"❌ 无法删除旧锁文件:{lock_path},请手动清理后重试。")
301
294
  sys.exit(1)
302
295
 
303
296
  # 原子创建锁文件,避免并发竞争
@@ -320,18 +313,12 @@ def _acquire_single_instance_lock(lock_name: str = "instance.lock") -> None:
320
313
  # 极端并发下再次校验
321
314
  pid = _read_lock_owner_pid(lock_path)
322
315
  if pid and _is_process_alive(pid):
323
- PrettyOutput.print(
324
- f"检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。",
325
- OutputType.WARNING,
326
- )
316
+ print(f"⚠️ 检测到已有一个 Jarvis 实例正在运行 (PID: {pid})。")
327
317
  sys.exit(0)
328
- PrettyOutput.print(
329
- f"锁文件已存在但可能为陈旧状态:{lock_path},请手动删除后重试。",
330
- OutputType.ERROR,
331
- )
318
+ print(f"❌ 锁文件已存在但可能为陈旧状态:{lock_path},请手动删除后重试。")
332
319
  sys.exit(1)
333
320
  except Exception as e:
334
- PrettyOutput.print(f"创建实例锁失败: {e}", OutputType.ERROR)
321
+ print(f"创建实例锁失败: {e}")
335
322
  sys.exit(1)
336
323
 
337
324
 
@@ -364,7 +351,7 @@ def _check_pip_updates() -> bool:
364
351
  with urllib.request.urlopen(url, timeout=5) as response:
365
352
  data = json.loads(response.read().decode())
366
353
  latest_version = data["info"]["version"]
367
- except (urllib.error.URLError, KeyError, json.JSONDecodeError):
354
+ except (urllib.error.URLError, KeyError, ValueError):
368
355
  return False
369
356
 
370
357
  # 比较版本
@@ -372,10 +359,7 @@ def _check_pip_updates() -> bool:
372
359
  latest_ver = version.parse(latest_version)
373
360
 
374
361
  if latest_ver > current_ver:
375
- PrettyOutput.print(
376
- f"检测到新版本 v{latest_version} (当前版本: v{__version__})",
377
- OutputType.INFO,
378
- )
362
+ print(f"ℹ️ 检测到新版本 v{latest_version} (当前版本: v{__version__})")
379
363
 
380
364
  # 检测是否在虚拟环境中
381
365
  hasattr(sys, "real_prefix") or (
@@ -422,7 +406,7 @@ def _check_pip_updates() -> bool:
422
406
 
423
407
  # 自动尝试升级(失败时提供手动命令)
424
408
  try:
425
- PrettyOutput.print("正在自动更新 Jarvis,请稍候...", OutputType.INFO)
409
+ print("ℹ️ 正在自动更新 Jarvis,请稍候...")
426
410
  result = subprocess.run(
427
411
  cmd_list,
428
412
  capture_output=True,
@@ -432,25 +416,18 @@ def _check_pip_updates() -> bool:
432
416
  timeout=600,
433
417
  )
434
418
  if result.returncode == 0:
435
- PrettyOutput.print("更新成功,正在重启以应用新版本...", OutputType.SUCCESS)
419
+ print("更新成功,正在重启以应用新版本...")
436
420
  # 更新检查日期,避免重复触发
437
421
  last_check_file.write_text(today_str)
438
422
  return True
439
423
  else:
440
424
  err = (result.stderr or result.stdout or "").strip()
441
425
  if err:
442
- PrettyOutput.print(
443
- f"自动更新失败,错误信息(已截断): {err[:500]}",
444
- OutputType.WARNING,
445
- )
446
- PrettyOutput.print(
447
- f"请手动执行以下命令更新: {update_cmd}", OutputType.INFO
448
- )
426
+ print(f"⚠️ 自动更新失败,错误信息(已截断): {err[:500]}")
427
+ print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
449
428
  except Exception:
450
- PrettyOutput.print("自动更新出现异常,已切换为手动更新方式。", OutputType.WARNING)
451
- PrettyOutput.print(
452
- f"请手动执行以下命令更新: {update_cmd}", OutputType.INFO
453
- )
429
+ print("⚠️ 自动更新出现异常,已切换为手动更新方式。")
430
+ print(f"ℹ️ 请手动执行以下命令更新: {update_cmd}")
454
431
 
455
432
  # 更新检查日期
456
433
  last_check_file.write_text(today_str)
@@ -491,8 +468,6 @@ def _check_jarvis_updates() -> bool:
491
468
 
492
469
  def _show_usage_stats(welcome_str: str) -> None:
493
470
  """显示Jarvis使用统计信息"""
494
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
495
-
496
471
  try:
497
472
 
498
473
  from rich.console import Console, Group
@@ -503,6 +478,7 @@ def _show_usage_stats(welcome_str: str) -> None:
503
478
  console = Console()
504
479
 
505
480
  from jarvis.jarvis_stats.stats import StatsManager
481
+ from jarvis.jarvis_stats.storage import StatsStorage
506
482
 
507
483
  # 获取所有可用的指标
508
484
  all_metrics = StatsManager.list_metrics()
@@ -518,19 +494,47 @@ def _show_usage_stats(welcome_str: str) -> None:
518
494
  "other": {"title": "📦 其他指标", "metrics": {}, "suffix": ""},
519
495
  }
520
496
 
521
- # 遍历所有指标,使用快速总量读取以避免全量扫描
522
- for metric in all_metrics:
497
+ # 复用存储实例,避免重复创建
498
+ storage = StatsStorage()
499
+
500
+ # 一次性读取元数据,避免重复读取
501
+ try:
502
+ meta = storage._load_json(storage.meta_file)
503
+ metrics_info = meta.get("metrics", {})
504
+ except Exception:
505
+ metrics_info = {}
506
+
507
+ # 批量读取所有总量文件,避免逐个文件操作
508
+ metric_totals: Dict[str, float] = {}
509
+ totals_dir = storage.totals_dir
510
+ if totals_dir.exists():
523
511
  try:
524
- total = StatsManager.get_metric_total(metric)
512
+ for total_file in totals_dir.glob("*"):
513
+ if total_file.is_file():
514
+ try:
515
+ with open(total_file, "r", encoding="utf-8") as f:
516
+ total = float((f.read() or "0").strip() or "0")
517
+ if total > 0:
518
+ metric_totals[total_file.name] = total
519
+ except Exception:
520
+ pass
525
521
  except Exception:
526
- total = 0.0
522
+ pass
527
523
 
524
+ # 遍历所有指标,使用批量读取的数据
525
+ for metric in all_metrics:
526
+ # 从批量读取的数据中获取总量
527
+ total = metric_totals.get(metric, 0.0)
528
+
528
529
  if not total or total <= 0:
529
530
  continue
530
531
 
531
- # 优先使用元信息中的分组(在写入指标时已记录)
532
- info = StatsManager.get_metric_info(metric) or {}
533
- group = info.get("group", "other")
532
+ # 从已加载的元数据中获取分组信息,避免重复读取
533
+ try:
534
+ info = metrics_info.get(metric, {})
535
+ group = info.get("group", "other")
536
+ except Exception:
537
+ group = "other"
534
538
 
535
539
  if group == "tool":
536
540
  categorized_stats["tool"]["metrics"][metric] = int(total)
@@ -893,11 +897,11 @@ def _show_usage_stats(welcome_str: str) -> None:
893
897
  # 输出错误信息以便调试
894
898
  import traceback
895
899
 
896
- PrettyOutput.print(f"统计显示出错: {str(e)}", OutputType.ERROR)
897
- PrettyOutput.print(traceback.format_exc(), OutputType.ERROR)
900
+ print(f"统计显示出错: {str(e)}")
901
+ print(f"❌ {traceback.format_exc()}")
898
902
 
899
903
 
900
- def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
904
+ def init_env(welcome_str: str = "", config_file: Optional[str] = None) -> None:
901
905
  """初始化Jarvis环境
902
906
 
903
907
  参数:
@@ -907,35 +911,60 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
907
911
  # 0. 检查是否处于Jarvis打开的终端环境,避免嵌套
908
912
  try:
909
913
  if os.environ.get("JARVIS_TERMINAL") == "1":
910
- PrettyOutput.print(
911
- "检测到当前终端由 Jarvis 打开。再次启动可能导致嵌套。",
912
- OutputType.WARNING,
913
- )
914
+ print("⚠️ 检测到当前终端由 Jarvis 打开。再次启动可能导致嵌套。")
914
915
  if not user_confirm("是否仍要继续启动 Jarvis?", default=False):
915
- PrettyOutput.print("已取消启动以避免终端嵌套。", OutputType.INFO)
916
+ print("ℹ️ 已取消启动以避免终端嵌套。")
916
917
  sys.exit(0)
917
918
  except Exception:
918
919
  pass
919
920
 
920
921
  # 1. 设置信号处理
921
- _setup_signal_handler()
922
+ try:
923
+ _setup_signal_handler()
924
+ except Exception:
925
+ pass
922
926
 
923
- # 2. 统计命令使用
924
- count_cmd_usage()
927
+ # 2. 统计命令使用(异步执行,避免阻塞初始化)
928
+ try:
929
+ count_cmd_usage()
930
+ except Exception:
931
+ # 静默失败,不影响正常使用
932
+ pass
925
933
 
926
934
  # 3. 设置配置文件
927
935
  global g_config_file
928
936
  g_config_file = config_file
929
- load_config()
937
+ try:
938
+ load_config()
939
+ except Exception:
940
+ # 静默失败,不影响正常使用
941
+ pass
930
942
 
931
943
  # 4. 显示历史统计数据(仅在显示欢迎信息时显示)
944
+ # 使用延迟加载,避免阻塞初始化
932
945
  if welcome_str:
933
- _show_usage_stats(welcome_str)
946
+ try:
947
+ # 在后台线程中显示统计,避免阻塞主流程
948
+ import threading
949
+ def show_stats_async():
950
+ try:
951
+ _show_usage_stats(welcome_str)
952
+ except Exception:
953
+ pass
954
+ stats_thread = threading.Thread(target=show_stats_async, daemon=True)
955
+ stats_thread.start()
956
+ except Exception:
957
+ # 静默失败,不影响正常使用
958
+ pass
934
959
 
935
- # 5. 检查Jarvis更新
936
- if _check_jarvis_updates():
937
- os.execv(sys.executable, [sys.executable] + sys.argv)
938
- sys.exit(0)
960
+ # 5. 检查Jarvis更新(异步执行,避免阻塞)
961
+ try:
962
+ if _check_jarvis_updates():
963
+ os.execv(sys.executable, [sys.executable] + sys.argv)
964
+ sys.exit(0)
965
+ except Exception:
966
+ # 静默失败,不影响正常使用
967
+ pass
939
968
 
940
969
 
941
970
  def _interactive_config_setup(config_file_path: Path):
@@ -947,9 +976,7 @@ def _interactive_config_setup(config_file_path: Path):
947
976
  user_confirm as get_yes_no,
948
977
  )
949
978
 
950
- PrettyOutput.print(
951
- "欢迎使用 Jarvis!未找到配置文件,现在开始引导配置。", OutputType.INFO
952
- )
979
+ print("ℹ️ 欢迎使用 Jarvis!未找到配置文件,现在开始引导配置。")
953
980
 
954
981
  # 1. 选择平台
955
982
  registry = PlatformRegistry.get_global_platform_registry()
@@ -959,7 +986,7 @@ def _interactive_config_setup(config_file_path: Path):
959
986
  # 2. 配置环境变量
960
987
  platform_class = registry.platforms.get(platform_name)
961
988
  if not platform_class:
962
- PrettyOutput.print(f"平台 '{platform_name}' 加载失败。", OutputType.ERROR)
989
+ print(f"平台 '{platform_name}' 加载失败。")
963
990
  sys.exit(1)
964
991
 
965
992
  env_vars = {}
@@ -967,9 +994,7 @@ def _interactive_config_setup(config_file_path: Path):
967
994
  defaults = platform_class.get_env_defaults()
968
995
  config_guide = platform_class.get_env_config_guide()
969
996
  if required_keys:
970
- PrettyOutput.print(
971
- f"请输入 {platform_name} 平台所需的配置信息:", OutputType.INFO
972
- )
997
+ print(f"ℹ️ 请输入 {platform_name} 平台所需的配置信息:")
973
998
 
974
999
  # 如果有配置指导,先显示总体说明
975
1000
  if config_guide:
@@ -980,7 +1005,7 @@ def _interactive_config_setup(config_file_path: Path):
980
1005
  guide_lines.append("")
981
1006
  guide_lines.append(f"{key} 获取方法:")
982
1007
  guide_lines.append(str(config_guide[key]))
983
- PrettyOutput.print("\n".join(guide_lines), OutputType.INFO)
1008
+ print("ℹ️ " + "\n".join(guide_lines))
984
1009
  else:
985
1010
  # 若无指导,仍需遍历以保持后续逻辑一致
986
1011
  pass
@@ -1002,7 +1027,7 @@ def _interactive_config_setup(config_file_path: Path):
1002
1027
  try:
1003
1028
  platform_instance = registry.create_platform(platform_name)
1004
1029
  if not platform_instance:
1005
- PrettyOutput.print(f"无法创建平台 '{platform_name}'。", OutputType.ERROR)
1030
+ print(f"无法创建平台 '{platform_name}'。")
1006
1031
  sys.exit(1)
1007
1032
 
1008
1033
  model_list_tuples = platform_instance.get_model_list()
@@ -1014,13 +1039,13 @@ def _interactive_config_setup(config_file_path: Path):
1014
1039
  model_name, _ = model_list_tuples[selected_index]
1015
1040
 
1016
1041
  except Exception:
1017
- PrettyOutput.print("获取模型列表失败", OutputType.ERROR)
1042
+ print("获取模型列表失败")
1018
1043
  if not get_yes_no("无法获取模型列表,是否继续配置?"):
1019
1044
  sys.exit(1)
1020
1045
  model_name = get_input("请输入模型名称:")
1021
1046
 
1022
1047
  # 4. 测试配置
1023
- PrettyOutput.print("正在测试配置...", OutputType.INFO)
1048
+ print("ℹ️ 正在测试配置...")
1024
1049
  test_passed = False
1025
1050
  try:
1026
1051
  platform_instance = registry.create_platform(platform_name)
@@ -1029,16 +1054,14 @@ def _interactive_config_setup(config_file_path: Path):
1029
1054
  response_generator = platform_instance.chat("hello")
1030
1055
  response = "".join(response_generator)
1031
1056
  if response:
1032
- PrettyOutput.print(
1033
- f"测试成功,模型响应: {response}", OutputType.SUCCESS
1034
- )
1057
+ print(f"✅ 测试成功,模型响应: {response}")
1035
1058
  test_passed = True
1036
1059
  else:
1037
- PrettyOutput.print("测试失败,模型没有响应。", OutputType.ERROR)
1060
+ print("测试失败,模型没有响应。")
1038
1061
  else:
1039
- PrettyOutput.print("测试失败,无法创建平台实例。", OutputType.ERROR)
1062
+ print("测试失败,无法创建平台实例。")
1040
1063
  except Exception:
1041
- PrettyOutput.print("测试失败", OutputType.ERROR)
1064
+ print("测试失败")
1042
1065
 
1043
1066
  # 5. 交互式确认并应用配置(不直接生成配置文件)
1044
1067
  config_data = {
@@ -1049,7 +1072,7 @@ def _interactive_config_setup(config_file_path: Path):
1049
1072
 
1050
1073
  if not test_passed:
1051
1074
  if not get_yes_no("配置测试失败,是否仍要应用该配置并继续?", default=False):
1052
- PrettyOutput.print("已取消配置。", OutputType.INFO)
1075
+ print("ℹ️ 已取消配置。")
1053
1076
  sys.exit(0)
1054
1077
 
1055
1078
  # 6. 选择其他功能开关与可选项(复用统一逻辑)
@@ -1072,11 +1095,11 @@ def _interactive_config_setup(config_file_path: Path):
1072
1095
  if header:
1073
1096
  f.write(header)
1074
1097
  f.write(yaml_str)
1075
- PrettyOutput.print(f"配置文件已生成: {config_file_path}", OutputType.SUCCESS)
1076
- PrettyOutput.print("配置完成,请重新启动Jarvis。", OutputType.INFO)
1098
+ print(f"配置文件已生成: {config_file_path}")
1099
+ print("ℹ️ 配置完成,请重新启动Jarvis。")
1077
1100
  sys.exit(0)
1078
1101
  except Exception:
1079
- PrettyOutput.print("写入配置文件失败", OutputType.ERROR)
1102
+ print("写入配置文件失败")
1080
1103
  sys.exit(1)
1081
1104
 
1082
1105
 
@@ -1380,6 +1403,12 @@ def _collect_session_config(config_data: dict, ask_all: bool) -> bool:
1380
1403
  "是否跳过预定义任务加载(不读取 pre-command 列表)?",
1381
1404
  False,
1382
1405
  ) or changed
1406
+ changed = _ask_config_int(
1407
+ config_data, ask_all,
1408
+ "JARVIS_CONVERSATION_TURN_THRESHOLD",
1409
+ "对话轮次阈值(达到此轮次时触发总结,建议50-100):",
1410
+ 50,
1411
+ ) or changed
1383
1412
  return changed
1384
1413
 
1385
1414
 
@@ -1425,33 +1454,9 @@ def _collect_data_and_token_config(config_data: dict, ask_all: bool) -> bool:
1425
1454
  return changed
1426
1455
 
1427
1456
 
1428
- def _collect_planning_config(config_data: dict, ask_all: bool) -> bool:
1429
- """收集规划相关配置"""
1430
- changed = False
1431
- changed = _ask_config_bool(
1432
- config_data, ask_all,
1433
- "JARVIS_PLAN_ENABLED",
1434
- "是否默认启用任务规划?当 Agent 初始化时 plan 参数未指定,将从此配置加载",
1435
- True,
1436
- ) or changed
1437
- changed = _ask_config_int(
1438
- config_data, ask_all,
1439
- "JARVIS_PLAN_MAX_DEPTH",
1440
- "任务规划的最大层数(限制递归拆分深度,默认2;仅在启用规划时生效)",
1441
- 2,
1442
- ) or changed
1443
- return changed
1444
-
1445
-
1446
1457
  def _collect_advanced_config(config_data: dict, ask_all: bool) -> bool:
1447
1458
  """收集高级配置(自动总结、脚本超时等)"""
1448
1459
  changed = False
1449
- changed = _ask_config_int(
1450
- config_data, ask_all,
1451
- "JARVIS_AUTO_SUMMARY_ROUNDS",
1452
- "基于对话轮次的自动总结阈值(达到该轮次后自动总结并清理历史,默认50)",
1453
- 50,
1454
- ) or changed
1455
1460
  changed = _ask_config_int(
1456
1461
  config_data, ask_all,
1457
1462
  "JARVIS_SCRIPT_EXECUTION_TIMEOUT",
@@ -1553,37 +1558,10 @@ def _ask_git_check_mode(config_data: dict, ask_all: bool) -> bool:
1553
1558
  return False
1554
1559
 
1555
1560
 
1556
- def _ask_patch_format_mode(config_data: dict, ask_all: bool) -> bool:
1557
- """询问补丁格式模式"""
1558
- try:
1559
- _key = "JARVIS_PATCH_FORMAT"
1560
- if not ask_all and _key in config_data:
1561
- return False
1562
- from jarvis.jarvis_utils.input import get_choice
1563
- from jarvis.jarvis_utils.config import get_patch_format
1564
- current_mode = config_data.get(_key, get_patch_format())
1565
- choices = ["all", "search", "search_range"]
1566
- tip = (
1567
- "请选择补丁格式处理模式 (JARVIS_PATCH_FORMAT):\n"
1568
- "该设置影响 edit_file_handler 在处理补丁时允许的匹配方式。\n"
1569
- " - all: 同时支持 SEARCH 与 SEARCH_START/SEARCH_END 两种模式(默认)。\n"
1570
- " - search: 仅允许精确片段匹配(SEARCH)。更稳定,适合较弱模型或严格控制改动。\n"
1571
- " - search_range: 仅允许范围匹配(SEARCH_START/SEARCH_END)。更灵活,适合较强模型和块内细粒度修改。"
1572
- )
1573
- new_mode = get_choice(tip, choices)
1574
- if new_mode == current_mode:
1575
- return False
1576
- config_data[_key] = new_mode
1577
- return True
1578
- except Exception:
1579
- return False
1580
-
1581
-
1582
1561
  def _collect_git_config(config_data: dict, ask_all: bool) -> bool:
1583
1562
  """收集Git相关配置"""
1584
1563
  changed = False
1585
1564
  changed = _ask_git_check_mode(config_data, ask_all) or changed
1586
- changed = _ask_patch_format_mode(config_data, ask_all) or changed
1587
1565
  changed = _ask_config_optional_str(
1588
1566
  config_data, ask_all,
1589
1567
  "JARVIS_GIT_COMMIT_PROMPT",
@@ -1699,7 +1677,6 @@ def _collect_optional_config_interactively(
1699
1677
  changed = _collect_session_config(config_data, ask_all) or changed
1700
1678
  changed = _collect_safety_config(config_data, ask_all) or changed
1701
1679
  changed = _collect_data_and_token_config(config_data, ask_all) or changed
1702
- changed = _collect_planning_config(config_data, ask_all) or changed
1703
1680
  changed = _collect_advanced_config(config_data, ask_all) or changed
1704
1681
  changed = _collect_directory_config(config_data, ask_all) or changed
1705
1682
  changed = _collect_web_search_config(config_data, ask_all) or changed
@@ -1762,15 +1739,13 @@ def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
1762
1739
  # 更新全局配置
1763
1740
  set_global_env_data(config_data)
1764
1741
  except Exception:
1765
- PrettyOutput.print("加载配置文件失败", OutputType.ERROR)
1742
+ print("加载配置文件失败")
1766
1743
  if get_yes_no("配置文件格式错误,是否删除并重新配置?"):
1767
1744
  try:
1768
1745
  os.remove(config_file)
1769
- PrettyOutput.print(
1770
- "已删除损坏的配置文件,请重启Jarvis以重新配置。", OutputType.SUCCESS
1771
- )
1746
+ print("✅ 已删除损坏的配置文件,请重启Jarvis以重新配置。")
1772
1747
  except Exception:
1773
- PrettyOutput.print("删除配置文件失败", OutputType.ERROR)
1748
+ print("删除配置文件失败")
1774
1749
  sys.exit(1)
1775
1750
 
1776
1751
 
@@ -1927,69 +1902,113 @@ def _read_old_config_file(config_file):
1927
1902
  {str(k): str(v) for k, v in config_data.items() if v is not None}
1928
1903
  )
1929
1904
  set_global_env_data(config_data)
1930
- PrettyOutput.print(
1931
- "检测到旧格式配置文件,旧格式以后将不再支持,请尽快迁移到新格式",
1932
- OutputType.WARNING,
1933
- )
1905
+ print("⚠️ 检测到旧格式配置文件,旧格式以后将不再支持,请尽快迁移到新格式")
1906
+
1907
+
1908
+ # 线程本地存储,用于共享重试计数器
1909
+ _retry_context = threading.local()
1910
+
1934
1911
 
1912
+ def _get_retry_count() -> int:
1913
+ """获取当前线程的重试计数"""
1914
+ if not hasattr(_retry_context, 'count'):
1915
+ _retry_context.count = 0
1916
+ return _retry_context.count
1935
1917
 
1936
- def while_success(func: Callable[[], Any], sleep_time: float = 0.1, max_retries: int = 5) -> Any:
1918
+
1919
+ def _increment_retry_count() -> int:
1920
+ """增加重试计数并返回新的计数值"""
1921
+ if not hasattr(_retry_context, 'count'):
1922
+ _retry_context.count = 0
1923
+ _retry_context.count += 1
1924
+ return _retry_context.count
1925
+
1926
+
1927
+ def _reset_retry_count():
1928
+ """重置重试计数"""
1929
+ _retry_context.count = 0
1930
+
1931
+
1932
+ def while_success(func: Callable[[], Any]) -> Any:
1937
1933
  """循环执行函数直到成功(累计日志后统一打印,避免逐次加框)
1938
1934
 
1939
1935
  参数:
1940
1936
  func -- 要执行的函数
1941
- sleep_time -- 每次失败后的等待时间(秒)
1942
- max_retries -- 最大重试次数,默认5次
1943
1937
 
1944
1938
  返回:
1945
1939
  函数执行结果
1940
+
1941
+ 注意:
1942
+ 与while_true共享重试计数器,累计重试6次,使用指数退避(第一次等待1s)
1946
1943
  """
1944
+ MAX_RETRIES = 6
1947
1945
  result: Any = None
1948
- retry_count = 0
1949
- while retry_count < max_retries:
1946
+
1947
+ while True:
1950
1948
  try:
1951
1949
  result = func()
1950
+ _reset_retry_count() # 成功后重置计数器
1952
1951
  break
1953
1952
  except Exception as e:
1954
- retry_count += 1
1955
- if retry_count < max_retries:
1956
- PrettyOutput.print(
1957
- f"发生异常:\n{e}\n重试中 ({retry_count}/{max_retries}),等待 {sleep_time}s...",
1958
- OutputType.WARNING,
1959
- )
1960
- time.sleep(sleep_time)
1961
- continue
1953
+ retry_count = _increment_retry_count()
1954
+ if retry_count <= MAX_RETRIES:
1955
+ # 指数退避:第1次等待1s (2^0),第2次等待2s (2^1),第3次等待4s (2^2),第4次等待8s (2^3),第6次等待32s (2^5)
1956
+ sleep_time = 2 ** (retry_count - 1)
1957
+ if retry_count < MAX_RETRIES:
1958
+ print(f"⚠️ 发生异常:\n{e}\n重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s...")
1959
+ time.sleep(sleep_time)
1960
+ else:
1961
+ print(f"⚠️ 发生异常:\n{e}\n已达到最大重试次数 ({retry_count}/{MAX_RETRIES})")
1962
+ _reset_retry_count()
1963
+ raise
1964
+ else:
1965
+ _reset_retry_count()
1966
+ raise
1962
1967
  return result
1963
1968
 
1964
1969
 
1965
- def while_true(func: Callable[[], bool], sleep_time: float = 0.1, max_retries: int = 5) -> Any:
1970
+ def while_true(func: Callable[[], bool]) -> Any:
1966
1971
  """循环执行函数直到返回True(累计日志后统一打印,避免逐次加框)
1967
1972
 
1968
1973
  参数:
1969
1974
  func: 要执行的函数,必须返回布尔值
1970
- sleep_time: 每次失败后的等待时间(秒)
1971
- max_retries: 最大重试次数,默认5次
1972
1975
 
1973
1976
  返回:
1974
1977
  函数最终返回的True值
1975
1978
 
1976
1979
  注意:
1977
1980
  与while_success不同,此函数只检查返回是否为True,
1978
- 不捕获异常,异常会直接抛出
1981
+ 不捕获异常,异常会直接抛出。
1982
+ 与while_success共享重试计数器,累计重试6次,使用指数退避(第一次等待1s)
1979
1983
  """
1984
+ MAX_RETRIES = 6
1980
1985
  ret: bool = False
1981
- retry_count = 0
1982
- while retry_count < max_retries:
1983
- ret = func()
1984
- if ret:
1986
+
1987
+ while True:
1988
+ try:
1989
+ ret = func()
1990
+ if ret:
1991
+ _reset_retry_count() # 成功后重置计数器
1992
+ break
1993
+ except Exception:
1994
+ # 异常直接抛出,不捕获
1995
+ _reset_retry_count()
1996
+ raise
1997
+
1998
+ retry_count = _increment_retry_count()
1999
+ if retry_count <= MAX_RETRIES:
2000
+ # 指数退避:第1次等待1s (2^0),第2次等待2s (2^1),第3次等待4s (2^2),第4次等待8s (2^3),第6次等待32s (2^5)
2001
+ sleep_time = 2 ** (retry_count - 1)
2002
+ if retry_count < MAX_RETRIES:
2003
+ print(f"⚠️ 返回空值,重试中 ({retry_count}/{MAX_RETRIES}),等待 {sleep_time}s...")
2004
+ time.sleep(sleep_time)
2005
+ else:
2006
+ print(f"⚠️ 返回空值,已达到最大重试次数 ({retry_count}/{MAX_RETRIES})")
2007
+ _reset_retry_count()
2008
+ break
2009
+ else:
2010
+ _reset_retry_count()
1985
2011
  break
1986
- retry_count += 1
1987
- if retry_count < max_retries:
1988
- PrettyOutput.print(
1989
- f"返回空值,重试中 ({retry_count}/{max_retries}),等待 {sleep_time}s...",
1990
- OutputType.WARNING,
1991
- )
1992
- time.sleep(sleep_time)
1993
2012
  return ret
1994
2013
 
1995
2014
 
@@ -2056,14 +2075,52 @@ def count_cmd_usage() -> None:
2056
2075
 
2057
2076
 
2058
2077
  def is_context_overflow(
2059
- content: str, model_group_override: Optional[str] = None
2078
+ content: str,
2079
+ model_group_override: Optional[str] = None,
2080
+ platform: Optional[Any] = None
2060
2081
  ) -> bool:
2061
- """判断文件内容是否超出上下文限制"""
2062
- return get_context_token_count(content) > get_max_big_content_size(
2063
- model_group_override
2064
- )
2065
-
2066
-
2082
+ """判断文件内容是否超出上下文限制
2083
+
2084
+ 参数:
2085
+ content: 要检查的内容
2086
+ model_group_override: 模型组覆盖(可选)
2087
+ platform: 平台实例(可选),如果提供则使用剩余token数量判断
2088
+
2089
+ 返回:
2090
+ bool: 如果内容超出上下文限制返回True
2091
+ """
2092
+ # 快速长度预估:如果内容长度明显超过限制,直接返回True,无需精确计算token
2093
+ if content:
2094
+ # 粗略估算:假设平均每个token约4个字符,保守估计使用3.5个字符/token
2095
+ estimated_tokens = len(content) // 3.5
2096
+
2097
+ # 获取最大token限制
2098
+ max_tokens = get_max_big_content_size(model_group_override)
2099
+
2100
+ # 如果预估token数超过限制的150%,直接认为超出(避免精确计算)
2101
+ if estimated_tokens > max_tokens * 1.5:
2102
+ return True
2103
+
2104
+ # 如果预估token数小于限制的50%,直接认为安全
2105
+ if estimated_tokens < max_tokens * 0.5:
2106
+ return False
2107
+
2108
+ # 只有在预估结果不明确时,才进行精确的token计算
2109
+ content_tokens = get_context_token_count(content)
2110
+
2111
+ # 优先使用剩余token数量
2112
+ if platform is not None:
2113
+ try:
2114
+ remaining_tokens = platform.get_remaining_token_count()
2115
+ # 如果内容token数超过剩余token的80%,认为超出限制
2116
+ threshold = int(remaining_tokens * 0.8)
2117
+ if threshold > 0:
2118
+ return content_tokens > threshold
2119
+ except Exception:
2120
+ pass
2121
+
2122
+ # 回退方案:使用输入窗口限制
2123
+ return content_tokens > get_max_big_content_size(model_group_override)
2067
2124
  def get_loc_stats() -> str:
2068
2125
  """使用loc命令获取当前目录的代码统计信息
2069
2126
 
@@ -2131,16 +2188,10 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
2131
2188
  subprocess.TimeoutExpired,
2132
2189
  FileNotFoundError,
2133
2190
  ) as e:
2134
- PrettyOutput.print(
2135
- f"放弃 '{repo_path.name}' 的更改失败: {str(e)}",
2136
- OutputType.ERROR,
2137
- )
2191
+ print(f"❌ 放弃 '{repo_path.name}' 的更改失败: {str(e)}")
2138
2192
  return
2139
2193
  else:
2140
- PrettyOutput.print(
2141
- f"跳过更新 '{repo_path.name}' 以保留未提交的更改。",
2142
- OutputType.INFO,
2143
- )
2194
+ print(f"ℹ️ 跳过更新 '{repo_path.name}' 以保留未提交的更改。")
2144
2195
  return
2145
2196
 
2146
2197
  # 获取更新前的commit hash
@@ -2193,25 +2244,17 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
2193
2244
  after_hash = after_hash_result.stdout.strip()
2194
2245
 
2195
2246
  if before_hash != after_hash:
2196
- PrettyOutput.print(
2197
- f"{repo_type}库 '{repo_path.name}' 已更新。", OutputType.SUCCESS
2198
- )
2247
+ print(f"✅ {repo_type}库 '{repo_path.name}' 已更新。")
2199
2248
 
2200
2249
  except FileNotFoundError:
2201
- PrettyOutput.print(
2202
- f"git 命令未找到,跳过更新 '{repo_path.name}'。", OutputType.WARNING
2203
- )
2250
+ print(f"⚠️ git 命令未找到,跳过更新 '{repo_path.name}'。")
2204
2251
  except subprocess.TimeoutExpired:
2205
- PrettyOutput.print(f"更新 '{repo_path.name}' 超时。", OutputType.ERROR)
2252
+ print(f"更新 '{repo_path.name}' 超时。")
2206
2253
  except subprocess.CalledProcessError as e:
2207
2254
  error_message = e.stderr.strip() if e.stderr else str(e)
2208
- PrettyOutput.print(
2209
- f"更新 '{repo_path.name}' 失败: {error_message}", OutputType.ERROR
2210
- )
2255
+ print(f"❌ 更新 '{repo_path.name}' 失败: {error_message}")
2211
2256
  except Exception as e:
2212
- PrettyOutput.print(
2213
- f"更新 '{repo_path.name}' 时发生未知错误: {str(e)}", OutputType.ERROR
2214
- )
2257
+ print(f"❌ 更新 '{repo_path.name}' 时发生未知错误: {str(e)}")
2215
2258
 
2216
2259
 
2217
2260
  def daily_check_git_updates(repo_dirs: List[str], repo_type: str):
@@ -2244,4 +2287,4 @@ def daily_check_git_updates(repo_dirs: List[str], repo_type: str):
2244
2287
  try:
2245
2288
  last_check_file.write_text(str(time.time()))
2246
2289
  except IOError as e:
2247
- PrettyOutput.print(f"无法写入git更新检查时间戳: {e}", OutputType.WARNING)
2290
+ print(f"⚠️ 无法写入git更新检查时间戳: {e}")