jarvis-ai-assistant 0.7.8__py3-none-any.whl → 1.0.2__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 (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -7,55 +7,85 @@ AgentRunLoop: 承载 Agent 的主运行循环逻辑。
7
7
  - 暂不变更外部调用入口,后续在 Agent._main_loop 中委派到该类
8
8
  - 保持与现有异常处理、工具调用、用户交互完全一致
9
9
  """
10
+
10
11
  import os
11
12
  from enum import Enum
12
- from typing import Any, TYPE_CHECKING
13
+ from typing import TYPE_CHECKING
14
+ from typing import Any
15
+ from typing import Optional
16
+
17
+ from rich import box
18
+ from rich.panel import Panel
13
19
 
14
- from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL, AFTER_TOOL_CALL
15
- from jarvis.jarvis_agent.utils import join_prompts, is_auto_complete, normalize_next_action
16
- from jarvis.jarvis_utils.config import get_max_input_token_count, get_conversation_turn_threshold
20
+ from jarvis.jarvis_agent.events import AFTER_TOOL_CALL
21
+ from jarvis.jarvis_agent.events import BEFORE_TOOL_CALL
22
+ from jarvis.jarvis_agent.utils import is_auto_complete
23
+ from jarvis.jarvis_agent.utils import join_prompts
24
+ from jarvis.jarvis_agent.utils import normalize_next_action
25
+ from jarvis.jarvis_utils.config import get_conversation_turn_threshold
26
+ from jarvis.jarvis_utils.config import get_max_input_token_count
27
+ from jarvis.jarvis_utils.output import PrettyOutput
28
+ from jarvis.jarvis_utils.tag import ot
17
29
 
18
30
  if TYPE_CHECKING:
19
31
  # 仅用于类型标注,避免运行时循环依赖
20
- from . import Agent # noqa: F401
32
+ from . import Agent
21
33
 
22
34
 
23
35
  class AgentRunLoop:
24
36
  def __init__(self, agent: "Agent") -> None:
25
37
  self.agent = agent
26
- self.conversation_rounds = 0
27
- self.tool_reminder_rounds = int(os.environ.get("JARVIS_TOOL_REMINDER_ROUNDS", 20))
38
+ self.tool_reminder_rounds = int(os.environ.get("tool_reminder_rounds", 20))
28
39
  # 基于剩余token数量的自动总结阈值:当剩余token低于输入窗口的20%时触发
29
40
  max_input_tokens = get_max_input_token_count(self.agent.model_group)
30
41
  self.summary_remaining_token_threshold = int(max_input_tokens * 0.2)
31
42
  self.conversation_turn_threshold = get_conversation_turn_threshold()
32
43
 
44
+ # Git diff相关属性
45
+ self._git_diff: Optional[str] = None # 缓存git diff内容
46
+
33
47
  def run(self) -> Any:
34
48
  """主运行循环(委派到传入的 agent 实例的方法与属性)"""
35
49
  run_input_handlers = True
36
50
 
37
51
  while True:
38
52
  try:
39
- self.conversation_rounds += 1
40
- if self.conversation_rounds % self.tool_reminder_rounds == 0:
53
+ current_round = self.agent.model.get_conversation_turn()
54
+ if current_round % self.tool_reminder_rounds == 0:
41
55
  self.agent.session.addon_prompt = join_prompts(
42
- [self.agent.session.addon_prompt, self.agent.get_tool_usage_prompt()]
56
+ [
57
+ self.agent.session.addon_prompt,
58
+ self.agent.get_tool_usage_prompt(),
59
+ ]
43
60
  )
44
61
  # 基于剩余token数量或对话轮次的自动总结判断
45
62
  remaining_tokens = self.agent.model.get_remaining_token_count()
46
63
  should_summarize = (
47
- remaining_tokens <= self.summary_remaining_token_threshold or
48
- self.conversation_rounds > self.conversation_turn_threshold
64
+ remaining_tokens <= self.summary_remaining_token_threshold
65
+ or current_round > self.conversation_turn_threshold
49
66
  )
50
67
  if should_summarize:
68
+ # 在总结前获取git diff(仅对CodeAgent类型)
69
+ try:
70
+ if (
71
+ hasattr(self.agent, "start_commit")
72
+ and self.agent.start_commit
73
+ ):
74
+ self._git_diff = self.get_git_diff()
75
+ else:
76
+ self._git_diff = None
77
+ except Exception as e:
78
+ PrettyOutput.auto_print(f"⚠️ 获取git diff失败: {str(e)}")
79
+ self._git_diff = f"获取git diff失败: {str(e)}"
80
+
51
81
  summary_text = self.agent._summarize_and_clear_history()
52
82
  if summary_text:
53
83
  # 将摘要作为下一轮的附加提示加入,从而维持上下文连续性
54
84
  self.agent.session.addon_prompt = join_prompts(
55
85
  [self.agent.session.addon_prompt, summary_text]
56
86
  )
57
- # 重置轮次计数(用于工具提醒)与对话长度计数器(用于摘要触发),开始新一轮周期
58
- self.conversation_rounds = 0
87
+ # 重置对话长度计数器(用于摘要触发),开始新一轮周期
88
+ # 注意:对话轮次由模型内部管理,这里不需要重置
59
89
  self.agent.session.conversation_length = 0
60
90
 
61
91
  ag = self.agent
@@ -77,11 +107,23 @@ class AgentRunLoop:
77
107
  ag.session.prompt = ""
78
108
  run_input_handlers = False
79
109
 
80
- # 检查是否包含 <!!!SUMMARY!!!> 标记,触发总结并清空历史
81
- if "<!!!SUMMARY!!!>" in current_response:
82
- print("ℹ️ 检测到 <!!!SUMMARY!!!> 标记,正在触发总结并清空历史...")
110
+ if ot("!!!SUMMARY!!!") in current_response:
111
+ PrettyOutput.auto_print(
112
+ f"ℹ️ 检测到 {ot('!!!SUMMARY!!!')} 标记,正在触发总结并清空历史..."
113
+ )
83
114
  # 移除标记,避免在后续处理中出现
84
- current_response = current_response.replace("<!!!SUMMARY!!!>", "").strip()
115
+ current_response = current_response.replace(
116
+ ot("!!!SUMMARY!!!"), ""
117
+ ).strip()
118
+ # 在总结前获取git diff(仅对CodeAgent类型)
119
+ try:
120
+ if hasattr(ag, "start_commit") and ag.start_commit:
121
+ self._git_diff = self.get_git_diff()
122
+ else:
123
+ self._git_diff = None
124
+ except Exception as e:
125
+ PrettyOutput.auto_print(f"⚠️ 获取git diff失败: {str(e)}")
126
+ self._git_diff = f"获取git diff失败: {str(e)}"
85
127
  # 触发总结并清空历史
86
128
  summary_text = ag._summarize_and_clear_history()
87
129
  if summary_text:
@@ -89,8 +131,8 @@ class AgentRunLoop:
89
131
  ag.session.addon_prompt = join_prompts(
90
132
  [ag.session.addon_prompt, summary_text]
91
133
  )
92
- # 重置轮次计数(用于工具提醒)与对话长度计数器(用于摘要触发),开始新一轮周期
93
- self.conversation_rounds = 0
134
+ # 重置对话长度计数器(用于摘要触发),开始新一轮周期
135
+ # 注意:对话轮次由模型内部管理,这里不需要重置
94
136
  ag.session.conversation_length = 0
95
137
  # 如果响应中还有其他内容,继续处理;否则继续下一轮
96
138
  if not current_response:
@@ -104,7 +146,9 @@ class AgentRunLoop:
104
146
  ):
105
147
  # 中断处理器请求跳过本轮剩余部分,直接开始下一次循环
106
148
  continue
107
- elif interrupt_result is not None and not isinstance(interrupt_result, Enum):
149
+ elif interrupt_result is not None and not isinstance(
150
+ interrupt_result, Enum
151
+ ):
108
152
  # 中断处理器返回了最终结果,任务结束
109
153
  return interrupt_result
110
154
 
@@ -127,15 +171,17 @@ class AgentRunLoop:
127
171
 
128
172
  # 将上一个提示和工具提示安全地拼接起来(仅当工具结果为字符串时)
129
173
  safe_tool_prompt = tool_prompt if isinstance(tool_prompt, str) else ""
130
-
174
+
131
175
  ag.session.prompt = join_prompts([ag.session.prompt, safe_tool_prompt])
132
176
 
133
177
  # 关键流程:直接调用 after_tool_call 回调函数
134
178
  try:
135
179
  # 获取所有订阅了 AFTER_TOOL_CALL 事件的回调
136
180
  listeners = ag.event_bus._listeners.get(AFTER_TOOL_CALL, [])
137
- for callback in listeners:
181
+ for listener_tuple in listeners:
138
182
  try:
183
+ # listener_tuple 是 (priority, order, callback)
184
+ _, _, callback = listener_tuple
139
185
  callback(
140
186
  agent=ag,
141
187
  current_response=current_response,
@@ -146,7 +192,7 @@ class AgentRunLoop:
146
192
  pass
147
193
  except Exception:
148
194
  pass
149
-
195
+
150
196
  # 非关键流程:广播工具调用后的事件(用于日志、监控等)
151
197
  try:
152
198
  ag.event_bus.emit(
@@ -174,10 +220,9 @@ class AgentRunLoop:
174
220
  return current_response
175
221
  return result
176
222
 
177
-
178
223
  # 检查是否有工具调用:如果tool_prompt不为空,说明有工具被调用
179
224
  has_tool_call = bool(safe_tool_prompt and safe_tool_prompt.strip())
180
-
225
+
181
226
  # 在非交互模式下,跟踪连续没有工具调用的次数
182
227
  if ag.non_interactive:
183
228
  if has_tool_call:
@@ -186,15 +231,28 @@ class AgentRunLoop:
186
231
  else:
187
232
  # 没有工具调用,增加计数器
188
233
  ag._no_tool_call_count += 1
189
- # 如果连续5次没有工具调用,添加工具使用提示
190
- if ag._no_tool_call_count >= 5:
234
+ # 如果连续3次没有工具调用,发送工具使用提示
235
+ if ag._no_tool_call_count >= 3:
191
236
  tool_usage_prompt = ag.get_tool_usage_prompt()
192
- ag.session.addon_prompt = join_prompts(
193
- [ag.session.addon_prompt, tool_usage_prompt]
194
- )
237
+ ag.set_addon_prompt(tool_usage_prompt)
195
238
  # 重置计数器,避免重复添加
196
239
  ag._no_tool_call_count = 0
197
240
 
241
+ # 如果没有工具调用,显示完整响应
242
+ if not has_tool_call and current_response and current_response.strip():
243
+ import jarvis.jarvis_utils.globals as G
244
+ from jarvis.jarvis_utils.globals import console
245
+
246
+ agent_name = ag.name if hasattr(ag, "name") else None
247
+ panel = Panel(
248
+ current_response,
249
+ title=f"[bold cyan]{(G.get_current_agent_name() + ' · ') if G.get_current_agent_name() else ''}{agent_name or 'LLM'}[/bold cyan]",
250
+ border_style="bright_blue",
251
+ box=box.ROUNDED,
252
+ expand=True,
253
+ )
254
+ console.print(panel)
255
+
198
256
  # 获取下一步用户输入
199
257
  next_action = ag._get_next_user_action()
200
258
  action = normalize_next_action(next_action)
@@ -205,5 +263,107 @@ class AgentRunLoop:
205
263
  return ag._complete_task(auto_completed=False)
206
264
 
207
265
  except Exception as e:
208
- print(f"❌ 任务失败: {str(e)}")
266
+ PrettyOutput.auto_print(f"❌ 任务失败: {str(e)}")
209
267
  return f"Task failed: {str(e)}"
268
+
269
+ def get_git_diff(self) -> str:
270
+ """获取从起始commit到当前commit的git diff
271
+
272
+ 返回:
273
+ str: git diff内容,如果无法获取则返回错误信息
274
+ """
275
+ try:
276
+ from jarvis.jarvis_utils.git_utils import get_diff_between_commits
277
+ from jarvis.jarvis_utils.git_utils import get_latest_commit_hash
278
+
279
+ # 获取agent实例
280
+ agent = self.agent
281
+
282
+ # 检查agent是否有start_commit属性
283
+ if not hasattr(agent, "start_commit") or not agent.start_commit:
284
+ return "无法获取起始commit哈希值"
285
+
286
+ start_commit = agent.start_commit
287
+ current_commit = get_latest_commit_hash()
288
+
289
+ if not current_commit:
290
+ return "无法获取当前commit哈希值"
291
+
292
+ if start_commit == current_commit:
293
+ return (
294
+ "# 没有检测到代码变更\n\n起始commit和当前commit相同,没有代码变更。"
295
+ )
296
+
297
+ # 获取diff
298
+ diff_content = get_diff_between_commits(start_commit, current_commit)
299
+
300
+ # 检查并处理token数量限制
301
+ model_group = agent.model_group
302
+ return self._check_diff_token_limit(diff_content, model_group)
303
+
304
+ except Exception as e:
305
+ return f"获取git diff失败: {str(e)}"
306
+
307
+ def get_cached_git_diff(self) -> Optional[str]:
308
+ """获取已缓存的git diff信息
309
+
310
+ 返回:
311
+ Optional[str]: 已缓存的git diff内容,如果尚未获取则返回None
312
+ """
313
+ return self._git_diff
314
+
315
+ def has_git_diff(self) -> bool:
316
+ """检查是否有可用的git diff信息
317
+
318
+ 返回:
319
+ bool: 如果有可用的git diff信息返回True,否则返回False
320
+ """
321
+ return self._git_diff is not None and bool(self._git_diff.strip())
322
+
323
+ def _check_diff_token_limit(
324
+ self, diff_content: str, model_group: Optional[str]
325
+ ) -> str:
326
+ """检查diff内容的token限制并返回适当的diff内容
327
+
328
+ 参数:
329
+ diff_content: 原始的diff内容
330
+ model_group: 模型组名称,可为空
331
+
332
+ 返回:
333
+ str: 处理后的diff内容(可能是原始内容或截断后的内容)
334
+ """
335
+ from jarvis.jarvis_utils.embedding import get_context_token_count
336
+
337
+ # 检查token数量限制
338
+ max_input_tokens = get_max_input_token_count(model_group)
339
+ # 预留一部分token用于其他内容,使用10%作为diff的限制
340
+ max_diff_tokens = int(max_input_tokens * 0.1)
341
+
342
+ diff_token_count = get_context_token_count(diff_content)
343
+
344
+ if diff_token_count <= max_diff_tokens:
345
+ return diff_content
346
+
347
+ # 如果diff内容太大,进行截断
348
+ lines = diff_content.split("\n")
349
+ truncated_lines = []
350
+ current_tokens = 0
351
+
352
+ for line in lines:
353
+ line_tokens = get_context_token_count(line)
354
+ if current_tokens + line_tokens > max_diff_tokens:
355
+ # 添加截断提示
356
+ truncated_lines.append("")
357
+ truncated_lines.append("# ⚠️ diff内容过大,已截断显示")
358
+ truncated_lines.append(
359
+ f"# 原始diff共 {len(lines)} 行,{diff_token_count} tokens"
360
+ )
361
+ truncated_lines.append(
362
+ f"# 显示前 {len(truncated_lines)} 行,约 {current_tokens} tokens"
363
+ )
364
+ break
365
+
366
+ truncated_lines.append(line)
367
+ current_tokens += line_tokens
368
+
369
+ return "\n".join(truncated_lines)
@@ -1,7 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import os
3
- from typing import Any, Dict, Optional, TYPE_CHECKING
3
+ from typing import TYPE_CHECKING
4
+ from typing import Any
5
+ from typing import Dict
6
+ from typing import Optional
4
7
 
8
+ from jarvis.jarvis_utils.output import PrettyOutput
5
9
 
6
10
  if TYPE_CHECKING:
7
11
  from jarvis.jarvis_platform.base import BasePlatform
@@ -60,9 +64,9 @@ class SessionManager:
60
64
  if self.model.restore(session_file):
61
65
  try:
62
66
  os.remove(session_file)
63
- print("✅ 会话已恢复,并已删除会话文件。")
67
+ PrettyOutput.auto_print("✅ 会话已恢复,并已删除会话文件。")
64
68
  except OSError as e:
65
- print(f"❌ 删除会话文件失败: {e}")
69
+ PrettyOutput.auto_print(f"❌ 删除会话文件失败: {e}")
66
70
  return True
67
71
  return False
68
72
 
@@ -1,14 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """分享管理模块,负责工具和方法论的分享功能"""
3
+
3
4
  import os
4
5
  import subprocess
5
- from typing import List, Dict, Any, Set
6
- from abc import ABC, abstractmethod
6
+ from abc import ABC
7
+ from abc import abstractmethod
8
+ from typing import Any
9
+ from typing import Dict
10
+ from typing import List
11
+ from typing import Set
7
12
 
8
13
  from prompt_toolkit import prompt
9
14
 
10
15
  from jarvis.jarvis_agent import user_confirm
11
16
  from jarvis.jarvis_utils.config import get_data_dir
17
+ from jarvis.jarvis_utils.output import PrettyOutput
12
18
 
13
19
 
14
20
  def parse_selection(selection_str: str, max_value: int) -> List[int]:
@@ -61,7 +67,7 @@ class ShareManager(ABC):
61
67
  def update_central_repo(self) -> None:
62
68
  """克隆或更新中心仓库"""
63
69
  if not os.path.exists(self.repo_path):
64
- print(f"ℹ️ 正在克隆中心{self.get_resource_type()}仓库...")
70
+ PrettyOutput.auto_print(f"ℹ️ 正在克隆中心{self.get_resource_type()}仓库...")
65
71
  subprocess.run(
66
72
  ["git", "clone", self.central_repo_url, self.repo_path], check=True
67
73
  )
@@ -90,7 +96,7 @@ class ShareManager(ABC):
90
96
  )
91
97
  subprocess.run(["git", "push"], cwd=self.repo_path, check=True)
92
98
  else:
93
- print(f"ℹ️ 正在更新中心{self.get_resource_type()}仓库...")
99
+ PrettyOutput.auto_print(f"ℹ️ 正在更新中心{self.get_resource_type()}仓库...")
94
100
  # 检查是否是空仓库
95
101
  try:
96
102
  # 先尝试获取远程分支信息
@@ -119,18 +125,22 @@ class ShareManager(ABC):
119
125
  ["git", "checkout", "."], cwd=self.repo_path, check=True
120
126
  )
121
127
  else:
122
- print(f"ℹ️ 跳过更新 '{self.repo_name}' 以保留未提交的更改。")
128
+ PrettyOutput.auto_print(
129
+ f"ℹ️ 跳过更新 '{self.repo_name}' 以保留未提交的更改。"
130
+ )
123
131
  return
124
132
  subprocess.run(["git", "pull"], cwd=self.repo_path, check=True)
125
133
  else:
126
- print(f"ℹ️ 中心{self.get_resource_type()}仓库是空的,将初始化为新仓库")
134
+ PrettyOutput.auto_print(
135
+ f"ℹ️ 中心{self.get_resource_type()}仓库是空的,将初始化为新仓库"
136
+ )
127
137
  except subprocess.CalledProcessError:
128
138
  # 如果命令失败,可能是网络问题或其他错误
129
- print("⚠️ 无法连接到远程仓库,将跳过更新")
139
+ PrettyOutput.auto_print("⚠️ 无法连接到远程仓库,将跳过更新")
130
140
 
131
141
  def commit_and_push(self, count: int) -> None:
132
142
  """提交并推送更改"""
133
- print("ℹ️ 正在提交更改...")
143
+ PrettyOutput.auto_print("ℹ️ 正在提交更改...")
134
144
  subprocess.run(["git", "add", "."], cwd=self.repo_path, check=True)
135
145
 
136
146
  commit_msg = f"Add {count} {self.get_resource_type()}(s) from local collection"
@@ -138,7 +148,7 @@ class ShareManager(ABC):
138
148
  ["git", "commit", "-m", commit_msg], cwd=self.repo_path, check=True
139
149
  )
140
150
 
141
- print("ℹ️ 正在推送到远程仓库...")
151
+ PrettyOutput.auto_print("ℹ️ 正在推送到远程仓库...")
142
152
  # 检查是否需要设置上游分支(空仓库的情况)
143
153
  try:
144
154
  # 先尝试普通推送
@@ -169,8 +179,8 @@ class ShareManager(ABC):
169
179
  resource_list.append(f"[{i}] {self.format_resource_display(resource)}")
170
180
 
171
181
  # 一次性打印所有资源
172
- joined_resources = '\n'.join(resource_list)
173
- print(f"ℹ️ {joined_resources}")
182
+ joined_resources = "\n".join(resource_list)
183
+ PrettyOutput.auto_print(f"ℹ️ {joined_resources}")
174
184
 
175
185
  # 让用户选择
176
186
  while True:
@@ -186,12 +196,12 @@ class ShareManager(ABC):
186
196
  else:
187
197
  selected_indices = parse_selection(choice_str, len(resources))
188
198
  if not selected_indices:
189
- print("⚠️ 无效的选择")
199
+ PrettyOutput.auto_print("⚠️ 无效的选择")
190
200
  continue
191
201
  return [resources[i - 1] for i in selected_indices]
192
202
 
193
203
  except ValueError:
194
- print("⚠️ 请输入有效的数字")
204
+ PrettyOutput.auto_print("⚠️ 请输入有效的数字")
195
205
 
196
206
  @abstractmethod
197
207
  def get_resource_type(self) -> str:
@@ -1,8 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
- from typing import Any, Tuple
2
+ from typing import Any
3
+ from typing import Tuple
3
4
 
4
- from jarvis.jarvis_utils.input import user_confirm
5
5
  from jarvis.jarvis_agent.utils import join_prompts
6
+ from jarvis.jarvis_utils.input import user_confirm
7
+ from jarvis.jarvis_utils.output import PrettyOutput
6
8
 
7
9
 
8
10
  def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
@@ -23,7 +25,7 @@ def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
23
25
 
24
26
  # Build script while stripping the no-confirm marker from each line
25
27
  script = "\n".join([_clean(c) for c in cmdline])
26
- print(script)
28
+ PrettyOutput.auto_print(script)
27
29
 
28
30
  # If any line contains the no-confirm marker, skip the pre-execution confirmation
29
31
  no_confirm = any(marker in c for c in cmdline)
@@ -41,11 +43,13 @@ def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
41
43
  )
42
44
  if user_confirm("是否将执行结果反馈给Agent?", default=True):
43
45
  return (
44
- join_prompts([
45
- user_input,
46
- f"用户执行以下脚本:\n{script}",
47
- f"执行结果:\n{output}",
48
- ]),
46
+ join_prompts(
47
+ [
48
+ user_input,
49
+ f"用户执行以下脚本:\n{script}",
50
+ f"执行结果:\n{output}",
51
+ ]
52
+ ),
49
53
  False,
50
54
  )
51
55
  return "", True
@@ -2,7 +2,7 @@
2
2
  """
3
3
  Web STDIO 重定向模块:
4
4
  - 在 Web 模式下,将 Python 层的标准输出/错误(sys.stdout/sys.stderr)重定向到 WebSocket,通过 WebBridge 广播。
5
- - 适用于工具或第三方库直接使用 print()/stdout/stderr 的输出,从而不经过 PrettyOutput Sink 的场景。
5
+ - 适用于工具或第三方库直接使用 PrettyOutput.auto_print()/stdout/stderr 的输出,从而不经过 PrettyOutput Sink 的场景。
6
6
 
7
7
  注意:
8
8
  - 这是进程级重定向,可能带来重复输出(PrettyOutput 已通过 Sink 广播一次,console.print 也会走到 stdout)。若需要避免重复,可在前端针对 'stdio' 类型进行独立显示或折叠。
@@ -17,14 +17,15 @@ Web STDIO 重定向模块:
17
17
  # ... 运行期间输出将通过 WS 广播 ...
18
18
  disable_web_stdio_redirect()
19
19
  """
20
+
20
21
  from __future__ import annotations
21
22
 
22
23
  import sys
23
24
  import threading
25
+ from typing import Optional
24
26
 
25
27
  from jarvis.jarvis_agent.web_bridge import WebBridge
26
28
 
27
-
28
29
  _original_stdout = sys.stdout
29
30
  _original_stderr = sys.stderr
30
31
  _redirect_enabled = False
@@ -47,11 +48,13 @@ class _WebStreamWrapper:
47
48
  except Exception:
48
49
  text = repr(s)
49
50
  try:
50
- WebBridge.instance().broadcast({
51
- "type": "stdio",
52
- "stream": self._stream_name,
53
- "text": text,
54
- })
51
+ WebBridge.instance().broadcast(
52
+ {
53
+ "type": "stdio",
54
+ "stream": self._stream_name,
55
+ "text": text,
56
+ }
57
+ )
55
58
  except Exception:
56
59
  # 广播异常不影响主流程
57
60
  pass
@@ -79,7 +82,10 @@ class _WebStreamWrapper:
79
82
  def __getattr__(self, name: str):
80
83
  # 兼容性:必要时委派到原始 stdout/stderr 的属性(尽量避免)
81
84
  try:
82
- return getattr(_original_stdout if self._stream_name == "stdout" else _original_stderr, name)
85
+ return getattr(
86
+ _original_stdout if self._stream_name == "stdout" else _original_stderr,
87
+ name,
88
+ )
83
89
  except Exception:
84
90
  raise AttributeError(name)
85
91
 
@@ -91,8 +97,8 @@ def enable_web_stdio_redirect() -> None:
91
97
  if _redirect_enabled:
92
98
  return
93
99
  try:
94
- sys.stdout = _WebStreamWrapper("stdout") # type: ignore[assignment]
95
- sys.stderr = _WebStreamWrapper("stderr") # type: ignore[assignment]
100
+ sys.stdout = _WebStreamWrapper("stdout")
101
+ sys.stderr = _WebStreamWrapper("stderr")
96
102
  _redirect_enabled = True
97
103
  except Exception:
98
104
  # 回退:保持原始输出
@@ -122,10 +128,9 @@ def disable_web_stdio_redirect() -> None:
122
128
  # - 仅适用于部分交互式场景(非真正 PTY 行为),可满足基础行缓冲输入
123
129
  from queue import Queue # noqa: E402
124
130
 
125
-
126
131
  _original_stdin = sys.stdin
127
132
  _stdin_enabled = False
128
- _stdin_wrapper = None # type: ignore[assignment]
133
+ _stdin_wrapper: Optional[_WebInputWrapper] = None
129
134
 
130
135
 
131
136
  class _WebInputWrapper:
@@ -136,7 +141,7 @@ class _WebInputWrapper:
136
141
  self._buffer: str = ""
137
142
  self._lock = threading.Lock()
138
143
  try:
139
- self._encoding = getattr(_original_stdin, "encoding", "utf-8") # type: ignore[name-defined]
144
+ self._encoding = getattr(_original_stdin, "encoding", "utf-8")
140
145
  except Exception:
141
146
  self._encoding = "utf-8"
142
147
 
@@ -172,10 +177,7 @@ class _WebInputWrapper:
172
177
  except Exception:
173
178
  chunk = ""
174
179
  if not isinstance(chunk, str):
175
- try:
176
- chunk = str(chunk)
177
- except Exception:
178
- chunk = ""
180
+ chunk = str(chunk) # type: ignore
179
181
  with self._lock:
180
182
  self._buffer += chunk
181
183
 
@@ -203,10 +205,7 @@ class _WebInputWrapper:
203
205
  except Exception:
204
206
  chunk = ""
205
207
  if not isinstance(chunk, str):
206
- try:
207
- chunk = str(chunk)
208
- except Exception:
209
- chunk = ""
208
+ chunk = str(chunk) # type: ignore
210
209
  with self._lock:
211
210
  self._buffer += chunk
212
211
 
@@ -260,14 +259,14 @@ def enable_web_stdin_redirect() -> None:
260
259
  try:
261
260
  # 记录原始 stdin(若尚未记录)
262
261
  if "_original_stdin" not in globals() or _original_stdin is None:
263
- _original_stdin = sys.stdin # type: ignore[assignment]
262
+ _original_stdin = sys.stdin
264
263
  _stdin_wrapper = _WebInputWrapper()
265
- sys.stdin = _stdin_wrapper # type: ignore[assignment]
264
+ sys.stdin = _stdin_wrapper
266
265
  _stdin_enabled = True
267
266
  except Exception:
268
267
  # 回退:保持原始输入
269
268
  try:
270
- sys.stdin = _original_stdin # type: ignore[assignment]
269
+ sys.stdin = _original_stdin
271
270
  except Exception:
272
271
  pass
273
272
  _stdin_enabled = False
@@ -278,7 +277,7 @@ def disable_web_stdin_redirect() -> None:
278
277
  global _stdin_enabled, _stdin_wrapper
279
278
  with _lock:
280
279
  try:
281
- sys.stdin = _original_stdin # type: ignore[assignment]
280
+ sys.stdin = _original_stdin
282
281
  except Exception:
283
282
  pass
284
283
  _stdin_wrapper = None
@@ -289,7 +288,7 @@ def feed_web_stdin(data: str) -> None:
289
288
  """向 Web STDIN 注入数据(由 WebSocket /stdio 端点调用)。"""
290
289
  try:
291
290
  if _stdin_enabled and _stdin_wrapper is not None:
292
- _stdin_wrapper.feed(data) # type: ignore[attr-defined]
291
+ _stdin_wrapper.feed(data)
293
292
  except Exception:
294
293
  # 注入失败不影响主流程
295
294
  pass