jarvis-ai-assistant 0.7.16__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.16.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/run_loop.py
CHANGED
|
@@ -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
|
|
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
|
|
15
|
-
from jarvis.jarvis_agent.
|
|
16
|
-
from jarvis.
|
|
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
|
|
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.
|
|
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.
|
|
40
|
-
if
|
|
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
|
-
[
|
|
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
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
110
|
+
if ot("!!!SUMMARY!!!") in current_response:
|
|
111
|
+
PrettyOutput.auto_print(
|
|
112
|
+
f"ℹ️ 检测到 {ot('!!!SUMMARY!!!')} 标记,正在触发总结并清空历史..."
|
|
113
|
+
)
|
|
83
114
|
# 移除标记,避免在后续处理中出现
|
|
84
|
-
current_response = current_response.replace(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
# 如果连续
|
|
190
|
-
if ag._no_tool_call_count >=
|
|
234
|
+
# 如果连续3次没有工具调用,发送工具使用提示
|
|
235
|
+
if ag._no_tool_call_count >= 3:
|
|
191
236
|
tool_usage_prompt = ag.get_tool_usage_prompt()
|
|
192
|
-
ag.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
67
|
+
PrettyOutput.auto_print("✅ 会话已恢复,并已删除会话文件。")
|
|
64
68
|
except OSError as e:
|
|
65
|
-
|
|
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
|
|
6
|
-
from abc import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
PrettyOutput.auto_print(
|
|
135
|
+
f"ℹ️ 中心{self.get_resource_type()}仓库是空的,将初始化为新仓库"
|
|
136
|
+
)
|
|
127
137
|
except subprocess.CalledProcessError:
|
|
128
138
|
# 如果命令失败,可能是网络问题或其他错误
|
|
129
|
-
|
|
139
|
+
PrettyOutput.auto_print("⚠️ 无法连接到远程仓库,将跳过更新")
|
|
130
140
|
|
|
131
141
|
def commit_and_push(self, count: int) -> None:
|
|
132
142
|
"""提交并推送更改"""
|
|
133
|
-
|
|
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
|
-
|
|
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 =
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
- 适用于工具或第三方库直接使用
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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(
|
|
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")
|
|
95
|
-
sys.stderr = _WebStreamWrapper("stderr")
|
|
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
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
262
|
+
_original_stdin = sys.stdin
|
|
264
263
|
_stdin_wrapper = _WebInputWrapper()
|
|
265
|
-
sys.stdin = _stdin_wrapper
|
|
264
|
+
sys.stdin = _stdin_wrapper
|
|
266
265
|
_stdin_enabled = True
|
|
267
266
|
except Exception:
|
|
268
267
|
# 回退:保持原始输入
|
|
269
268
|
try:
|
|
270
|
-
sys.stdin = _original_stdin
|
|
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
|
|
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)
|
|
291
|
+
_stdin_wrapper.feed(data)
|
|
293
292
|
except Exception:
|
|
294
293
|
# 注入失败不影响主流程
|
|
295
294
|
pass
|