jarvis-ai-assistant 0.3.26__py3-none-any.whl → 0.3.27__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 +290 -173
- jarvis/jarvis_agent/config.py +92 -0
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/jarvis.py +69 -35
- jarvis/jarvis_agent/memory_manager.py +70 -2
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/run_loop.py +130 -0
- jarvis/jarvis_agent/task_analyzer.py +88 -9
- jarvis/jarvis_agent/task_manager.py +26 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_code_agent/code_agent.py +18 -3
- jarvis/jarvis_code_agent/lint.py +5 -5
- jarvis/jarvis_data/config_schema.json +7 -6
- jarvis/jarvis_git_squash/main.py +6 -1
- jarvis/jarvis_git_utils/git_commiter.py +38 -12
- jarvis/jarvis_platform/base.py +4 -5
- jarvis/jarvis_platform_manager/main.py +28 -11
- jarvis/jarvis_stats/cli.py +13 -32
- jarvis/jarvis_stats/stats.py +179 -51
- jarvis/jarvis_tools/registry.py +15 -0
- jarvis/jarvis_tools/sub_agent.py +94 -84
- jarvis/jarvis_tools/sub_code_agent.py +12 -6
- jarvis/jarvis_utils/config.py +14 -0
- jarvis/jarvis_utils/fzf.py +56 -0
- jarvis/jarvis_utils/input.py +0 -3
- jarvis/jarvis_utils/utils.py +56 -8
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/METADATA +2 -3
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/RECORD +33 -27
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.27.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@
|
|
6
6
|
from typing import Optional
|
7
7
|
|
8
8
|
from jarvis.jarvis_utils.globals import get_interrupt, set_interrupt
|
9
|
-
|
9
|
+
|
10
10
|
from jarvis.jarvis_agent.prompts import TASK_ANALYSIS_PROMPT
|
11
11
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
12
12
|
|
@@ -22,6 +22,12 @@ class TaskAnalyzer:
|
|
22
22
|
agent: Agent实例
|
23
23
|
"""
|
24
24
|
self.agent = agent
|
25
|
+
self._analysis_done = False
|
26
|
+
# 旁路集成事件订阅,失败不影响主流程
|
27
|
+
try:
|
28
|
+
self._subscribe_events()
|
29
|
+
except Exception:
|
30
|
+
pass
|
25
31
|
|
26
32
|
def analysis_task(self, satisfaction_feedback: str = ""):
|
27
33
|
"""分析任务并生成方法论"""
|
@@ -40,6 +46,13 @@ class TaskAnalyzer:
|
|
40
46
|
|
41
47
|
except Exception as e:
|
42
48
|
PrettyOutput.print("分析失败", OutputType.ERROR)
|
49
|
+
finally:
|
50
|
+
# 标记已完成一次分析,避免事件回调重复执行
|
51
|
+
self._analysis_done = True
|
52
|
+
try:
|
53
|
+
self.agent.set_user_data("__task_analysis_done__", True)
|
54
|
+
except Exception:
|
55
|
+
pass
|
43
56
|
|
44
57
|
def _prepare_analysis_prompt(self, satisfaction_feedback: str) -> str:
|
45
58
|
"""准备分析提示"""
|
@@ -59,8 +72,27 @@ class TaskAnalyzer:
|
|
59
72
|
if not self._handle_analysis_interrupt(response):
|
60
73
|
break
|
61
74
|
|
62
|
-
#
|
63
|
-
|
75
|
+
# 执行工具调用(补充事件:before_tool_call/after_tool_call)
|
76
|
+
try:
|
77
|
+
self.agent.event_bus.emit(
|
78
|
+
"before_tool_call",
|
79
|
+
agent=self.agent,
|
80
|
+
current_response=response,
|
81
|
+
)
|
82
|
+
except Exception:
|
83
|
+
pass
|
84
|
+
need_return, tool_prompt = self.agent._call_tools(response)
|
85
|
+
self.agent.session.prompt = tool_prompt
|
86
|
+
try:
|
87
|
+
self.agent.event_bus.emit(
|
88
|
+
"after_tool_call",
|
89
|
+
agent=self.agent,
|
90
|
+
current_response=response,
|
91
|
+
need_return=need_return,
|
92
|
+
tool_prompt=tool_prompt,
|
93
|
+
)
|
94
|
+
except Exception:
|
95
|
+
pass
|
64
96
|
|
65
97
|
# 如果没有工具调用或者没有新的提示,退出循环
|
66
98
|
if not self.agent.session.prompt:
|
@@ -73,8 +105,8 @@ class TaskAnalyzer:
|
|
73
105
|
bool: True 继续分析,False 退出分析
|
74
106
|
"""
|
75
107
|
set_interrupt(False)
|
76
|
-
user_input = self.agent.
|
77
|
-
|
108
|
+
user_input = self.agent._multiline_input(
|
109
|
+
"分析任务期间被中断,请输入用户干预信息:", False
|
78
110
|
)
|
79
111
|
|
80
112
|
if not user_input:
|
@@ -98,7 +130,7 @@ class TaskAnalyzer:
|
|
98
130
|
|
99
131
|
def _handle_interrupt_with_tool_calls(self, user_input: str) -> str:
|
100
132
|
"""处理有工具调用时的中断"""
|
101
|
-
if user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
|
133
|
+
if self.agent.user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
|
102
134
|
return f"被用户中断,用户补充信息为:{user_input}\n\n用户同意继续工具调用。"
|
103
135
|
else:
|
104
136
|
return f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
@@ -108,11 +140,11 @@ class TaskAnalyzer:
|
|
108
140
|
satisfaction_feedback = ""
|
109
141
|
|
110
142
|
if not auto_completed and self.agent.use_analysis:
|
111
|
-
if user_confirm("您对本次任务的完成是否满意?", True):
|
143
|
+
if self.agent.user_confirm("您对本次任务的完成是否满意?", True):
|
112
144
|
satisfaction_feedback = "\n\n用户对本次任务的完成表示满意。"
|
113
145
|
else:
|
114
|
-
feedback = self.agent.
|
115
|
-
"请提供您的反馈意见(可留空直接回车):"
|
146
|
+
feedback = self.agent._multiline_input(
|
147
|
+
"请提供您的反馈意见(可留空直接回车):", False
|
116
148
|
)
|
117
149
|
if feedback:
|
118
150
|
satisfaction_feedback = (
|
@@ -124,3 +156,50 @@ class TaskAnalyzer:
|
|
124
156
|
)
|
125
157
|
|
126
158
|
return satisfaction_feedback
|
159
|
+
|
160
|
+
# -----------------------
|
161
|
+
# 事件订阅与处理(旁路)
|
162
|
+
# -----------------------
|
163
|
+
def _subscribe_events(self) -> None:
|
164
|
+
bus = self.agent.get_event_bus() # type: ignore[attr-defined]
|
165
|
+
# 在生成总结前触发(保持与原顺序一致)
|
166
|
+
bus.subscribe("before_summary", self._on_before_summary)
|
167
|
+
# 当无需总结时,作为兜底触发分析
|
168
|
+
bus.subscribe("task_completed", self._on_task_completed)
|
169
|
+
|
170
|
+
def _on_before_summary(self, **payload) -> None:
|
171
|
+
if self._analysis_done:
|
172
|
+
return
|
173
|
+
# 避免与直接调用重复
|
174
|
+
try:
|
175
|
+
if bool(self.agent.get_user_data("__task_analysis_done__")):
|
176
|
+
self._analysis_done = True
|
177
|
+
return
|
178
|
+
except Exception:
|
179
|
+
pass
|
180
|
+
auto_completed = bool(payload.get("auto_completed", False))
|
181
|
+
try:
|
182
|
+
feedback = self.collect_satisfaction_feedback(auto_completed)
|
183
|
+
if getattr(self.agent, "use_analysis", False):
|
184
|
+
self.analysis_task(feedback)
|
185
|
+
except Exception:
|
186
|
+
# 忽略事件处理异常,保证主流程
|
187
|
+
self._analysis_done = True
|
188
|
+
|
189
|
+
def _on_task_completed(self, **payload) -> None:
|
190
|
+
# 当未在 before_summary 阶段执行过时,作为兜底
|
191
|
+
if self._analysis_done:
|
192
|
+
return
|
193
|
+
try:
|
194
|
+
if bool(self.agent.get_user_data("__task_analysis_done__")):
|
195
|
+
self._analysis_done = True
|
196
|
+
return
|
197
|
+
except Exception:
|
198
|
+
pass
|
199
|
+
auto_completed = bool(payload.get("auto_completed", False))
|
200
|
+
try:
|
201
|
+
feedback = self.collect_satisfaction_feedback(auto_completed)
|
202
|
+
if getattr(self.agent, "use_analysis", False):
|
203
|
+
self.analysis_task(feedback)
|
204
|
+
except Exception:
|
205
|
+
self._analysis_done = True
|
@@ -15,6 +15,7 @@ from jarvis.jarvis_agent import (
|
|
15
15
|
user_confirm,
|
16
16
|
)
|
17
17
|
from jarvis.jarvis_utils.config import get_data_dir
|
18
|
+
from jarvis.jarvis_utils.fzf import fzf_select
|
18
19
|
|
19
20
|
|
20
21
|
class TaskManager:
|
@@ -89,6 +90,31 @@ class TaskManager:
|
|
89
90
|
Console().print(table)
|
90
91
|
PrettyOutput.print("[0] 跳过预定义任务", OutputType.INFO)
|
91
92
|
|
93
|
+
# Try fzf selection first (with numbered options and a skip option)
|
94
|
+
fzf_list = [f"{0:>3} | 跳过预定义任务"] + [
|
95
|
+
f"{i:>3} | {name}" for i, name in enumerate(task_names, 1)
|
96
|
+
]
|
97
|
+
selected_str = fzf_select(fzf_list, prompt="选择一个任务编号 (ESC跳过) > ")
|
98
|
+
if selected_str:
|
99
|
+
try:
|
100
|
+
num_part = selected_str.split("|", 1)[0].strip()
|
101
|
+
idx = int(num_part)
|
102
|
+
if idx == 0:
|
103
|
+
return ""
|
104
|
+
if 1 <= idx <= len(task_names):
|
105
|
+
selected_task = tasks[task_names[idx - 1]]
|
106
|
+
PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
|
107
|
+
# 询问是否需要补充信息
|
108
|
+
need_additional = user_confirm("需要为此任务添加补充信息吗?", default=False)
|
109
|
+
if need_additional:
|
110
|
+
additional_input = get_multiline_input("请输入补充信息:")
|
111
|
+
if additional_input:
|
112
|
+
selected_task = f"{selected_task}\n\n补充信息:\n{additional_input}"
|
113
|
+
return selected_task
|
114
|
+
except Exception:
|
115
|
+
# 如果解析失败,则回退到手动输入
|
116
|
+
pass
|
117
|
+
|
92
118
|
while True:
|
93
119
|
try:
|
94
120
|
choice_str = prompt(
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
UserInteractionHandler: 抽象用户交互(多行输入与确认)逻辑,便于将来替换为 TUI/WebUI。
|
4
|
+
|
5
|
+
阶段一(最小变更):
|
6
|
+
- 仅提供封装,不直接修改 Agent 的现有调用
|
7
|
+
- 后续步骤在 Agent 中以旁路方式接入,保持向后兼容
|
8
|
+
"""
|
9
|
+
from typing import Callable, Optional
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
class UserInteractionHandler:
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
multiline_inputer: Callable[..., str],
|
17
|
+
confirm_func: Callable[[str, bool], bool],
|
18
|
+
) -> None:
|
19
|
+
"""
|
20
|
+
参数:
|
21
|
+
- multiline_inputer: 提供多行输入的函数,优先支持 (tip, print_on_empty=bool),兼容仅接受 (tip) 的实现
|
22
|
+
- confirm_func: 用户确认函数 (tip: str, default: bool) -> bool
|
23
|
+
"""
|
24
|
+
self._multiline_inputer = multiline_inputer
|
25
|
+
self._confirm = confirm_func
|
26
|
+
|
27
|
+
def multiline_input(self, tip: str, print_on_empty: bool) -> str:
|
28
|
+
"""
|
29
|
+
多行输入封装:兼容两类签名
|
30
|
+
1) func(tip, print_on_empty=True/False)
|
31
|
+
2) func(tip)
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
return self._multiline_inputer(tip, print_on_empty=print_on_empty) # type: ignore[call-arg]
|
35
|
+
except TypeError:
|
36
|
+
return self._multiline_inputer(tip) # type: ignore[misc]
|
37
|
+
|
38
|
+
def confirm(self, tip: str, default: bool = True) -> bool:
|
39
|
+
"""
|
40
|
+
用户确认封装,直接委派
|
41
|
+
"""
|
42
|
+
return self._confirm(tip, default)
|
@@ -21,6 +21,7 @@ from jarvis.jarvis_tools.registry import ToolRegistry
|
|
21
21
|
from jarvis.jarvis_utils.config import (
|
22
22
|
is_confirm_before_apply_patch,
|
23
23
|
is_enable_static_analysis,
|
24
|
+
get_git_check_mode,
|
24
25
|
)
|
25
26
|
from jarvis.jarvis_utils.git_utils import (
|
26
27
|
confirm_add_new_files,
|
@@ -197,6 +198,17 @@ class CodeAgent:
|
|
197
198
|
missing_configs
|
198
199
|
)
|
199
200
|
PrettyOutput.print(message, OutputType.WARNING)
|
201
|
+
# 通过配置控制严格校验模式(JARVIS_GIT_CHECK_MODE):
|
202
|
+
# - warn: 仅告警并继续,后续提交可能失败
|
203
|
+
# - strict: 严格模式(默认),直接退出
|
204
|
+
mode = get_git_check_mode().lower()
|
205
|
+
if mode == "warn":
|
206
|
+
PrettyOutput.print(
|
207
|
+
"已启用 Git 校验警告模式(JARVIS_GIT_CHECK_MODE=warn),将继续运行。"
|
208
|
+
"注意:后续提交可能失败,请尽快配置 git user.name 与 user.email。",
|
209
|
+
OutputType.INFO,
|
210
|
+
)
|
211
|
+
return
|
200
212
|
sys.exit(1)
|
201
213
|
|
202
214
|
except FileNotFoundError:
|
@@ -250,7 +262,7 @@ class CodeAgent:
|
|
250
262
|
if has_uncommitted_changes():
|
251
263
|
|
252
264
|
git_commiter = GitCommitTool()
|
253
|
-
git_commiter.execute({"prefix": prefix, "suffix": suffix})
|
265
|
+
git_commiter.execute({"prefix": prefix, "suffix": suffix, "agent": self.agent})
|
254
266
|
|
255
267
|
def _init_env(self, prefix: str, suffix: str) -> None:
|
256
268
|
"""初始化环境,组合以下功能:
|
@@ -510,7 +522,7 @@ class CodeAgent:
|
|
510
522
|
check=True,
|
511
523
|
)
|
512
524
|
git_commiter = GitCommitTool()
|
513
|
-
git_commiter.execute({"prefix": prefix, "suffix": suffix})
|
525
|
+
git_commiter.execute({"prefix": prefix, "suffix": suffix, "agent": self.agent})
|
514
526
|
|
515
527
|
# 在用户接受commit后,根据配置决定是否保存记忆
|
516
528
|
if self.agent.force_save_memory:
|
@@ -544,7 +556,7 @@ class CodeAgent:
|
|
544
556
|
f"提交 {i+1}: {commit['hash'][:7]} - {commit['message']} ({len(commit['files'])}个文件)\n"
|
545
557
|
+ "\n".join(f" - {file}" for file in commit["files"][:5])
|
546
558
|
+ ("\n ..." if len(commit["files"]) > 5 else "")
|
547
|
-
for i, commit in enumerate(commits_info)
|
559
|
+
for i, commit in enumerate(commits_info[:5])
|
548
560
|
)
|
549
561
|
project_info.append(f"最近提交:\n{commits_str}")
|
550
562
|
|
@@ -575,6 +587,8 @@ class CodeAgent:
|
|
575
587
|
PrettyOutput.print(f"执行失败: {str(e)}", OutputType.WARNING)
|
576
588
|
return str(e)
|
577
589
|
|
590
|
+
|
591
|
+
|
578
592
|
self._handle_uncommitted_changes()
|
579
593
|
end_commit = get_latest_commit_hash()
|
580
594
|
commits = self._show_commit_history(start_commit, end_commit)
|
@@ -615,6 +629,7 @@ class CodeAgent:
|
|
615
629
|
|
616
630
|
StatsManager.increment("code_modifications", group="code_agent")
|
617
631
|
|
632
|
+
|
618
633
|
# 获取提交信息
|
619
634
|
end_hash = get_latest_commit_hash()
|
620
635
|
commits = get_commits_between(start_hash, end_hash)
|
jarvis/jarvis_code_agent/lint.py
CHANGED
@@ -27,11 +27,11 @@ LINT_TOOLS = {
|
|
27
27
|
# Go
|
28
28
|
".go": ["go vet"],
|
29
29
|
# Python
|
30
|
-
".py": ["
|
31
|
-
".pyw": ["
|
32
|
-
".pyi": ["
|
33
|
-
".pyx": ["
|
34
|
-
".pxd": ["
|
30
|
+
".py": ["ruff", "mypy"],
|
31
|
+
".pyw": ["ruff", "mypy"],
|
32
|
+
".pyi": ["ruff", "mypy"],
|
33
|
+
".pyx": ["ruff", "mypy"],
|
34
|
+
".pxd": ["ruff", "mypy"],
|
35
35
|
# Rust
|
36
36
|
".rs": ["cargo clippy", "rustfmt"],
|
37
37
|
".rlib": ["cargo clippy", "rustfmt"],
|
@@ -126,8 +126,6 @@
|
|
126
126
|
"description": "常规操作模型名称",
|
127
127
|
"default": "deep_seek_v3"
|
128
128
|
},
|
129
|
-
|
130
|
-
|
131
129
|
"JARVIS_WEB_SEARCH_PLATFORM": {
|
132
130
|
"type": "string",
|
133
131
|
"description": "Web搜索使用的平台名称",
|
@@ -160,8 +158,6 @@
|
|
160
158
|
"type": "string",
|
161
159
|
"default": "deep_seek_v3"
|
162
160
|
},
|
163
|
-
|
164
|
-
|
165
161
|
"JARVIS_MAX_INPUT_TOKEN_COUNT": {
|
166
162
|
"type": "number",
|
167
163
|
"default": 32000
|
@@ -196,7 +192,6 @@
|
|
196
192
|
"description": "Jarvis数据存储目录路径",
|
197
193
|
"default": "~/.jarvis"
|
198
194
|
},
|
199
|
-
|
200
195
|
"JARVIS_PRETTY_OUTPUT": {
|
201
196
|
"type": "boolean",
|
202
197
|
"description": "是否启用美化输出",
|
@@ -292,6 +287,12 @@
|
|
292
287
|
"description": "是否启用立即中断:在对话迭代中检测到中断信号时立即返回",
|
293
288
|
"default": false
|
294
289
|
},
|
290
|
+
"JARVIS_GIT_CHECK_MODE": {
|
291
|
+
"type": "string",
|
292
|
+
"enum": ["strict", "warn"],
|
293
|
+
"description": "Git 配置校验模式:strict 表示严格模式(默认),缺失配置时直接退出;warn 表示警告模式,仅提示并继续运行",
|
294
|
+
"default": "strict"
|
295
|
+
},
|
295
296
|
"JARVIS_TOOL_GROUP": {
|
296
297
|
"type": "string",
|
297
298
|
"description": "选择一个预定义的工具配置组",
|
@@ -445,4 +446,4 @@
|
|
445
446
|
}
|
446
447
|
},
|
447
448
|
"additionalProperties": true
|
448
|
-
}
|
449
|
+
}
|
jarvis/jarvis_git_squash/main.py
CHANGED
@@ -8,6 +8,7 @@ from jarvis.jarvis_git_utils.git_commiter import GitCommitTool
|
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
9
9
|
from jarvis.jarvis_utils.utils import init_env
|
10
10
|
from jarvis.jarvis_utils.input import user_confirm
|
11
|
+
from jarvis.jarvis_utils.globals import get_agent, current_agent_name
|
11
12
|
|
12
13
|
app = typer.Typer(help="Git压缩工具")
|
13
14
|
|
@@ -46,7 +47,11 @@ class GitSquashTool:
|
|
46
47
|
|
47
48
|
# Use existing GitCommitTool for new commit
|
48
49
|
commit_tool = GitCommitTool()
|
49
|
-
|
50
|
+
agent = get_agent(current_agent_name)
|
51
|
+
exec_args = {"lang": args.get("lang", "Chinese")}
|
52
|
+
if agent:
|
53
|
+
exec_args["agent"] = agent
|
54
|
+
commit_tool.execute(exec_args)
|
50
55
|
except Exception as e:
|
51
56
|
PrettyOutput.print(f"压缩提交失败: {str(e)}", OutputType.WARNING)
|
52
57
|
|
@@ -163,6 +163,9 @@ commit信息
|
|
163
163
|
{ct("COMMIT_MESSAGE")}
|
164
164
|
"""
|
165
165
|
|
166
|
+
# 优先从调用方传入的 agent 获取平台与模型
|
167
|
+
agent_from_args = args.get("agent")
|
168
|
+
|
166
169
|
# Get model_group from args
|
167
170
|
model_group = args.get("model_group")
|
168
171
|
|
@@ -172,21 +175,44 @@ commit信息
|
|
172
175
|
get_normal_model_name,
|
173
176
|
)
|
174
177
|
|
175
|
-
platform_name =
|
176
|
-
model_name =
|
178
|
+
platform_name = None
|
179
|
+
model_name = None
|
177
180
|
|
178
|
-
# If no explicit parameters, try to get from existing agent
|
179
|
-
agent = get_agent(current_agent_name)
|
180
181
|
if (
|
181
|
-
|
182
|
-
and
|
183
|
-
and
|
184
|
-
and agent.model
|
182
|
+
agent_from_args
|
183
|
+
and hasattr(agent_from_args, "model")
|
184
|
+
and getattr(agent_from_args, "model", None)
|
185
185
|
):
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
model_group
|
186
|
+
try:
|
187
|
+
platform_name = agent_from_args.model.platform_name()
|
188
|
+
model_name = agent_from_args.model.name()
|
189
|
+
if not model_group and hasattr(
|
190
|
+
agent_from_args.model, "model_group"
|
191
|
+
):
|
192
|
+
model_group = agent_from_args.model.model_group
|
193
|
+
except Exception:
|
194
|
+
# 安全回退到后续逻辑
|
195
|
+
platform_name = None
|
196
|
+
model_name = None
|
197
|
+
|
198
|
+
# 如果未能从agent获取到,再根据 model_group 获取
|
199
|
+
if not platform_name:
|
200
|
+
platform_name = get_normal_platform_name(model_group)
|
201
|
+
if not model_name:
|
202
|
+
model_name = get_normal_model_name(model_group)
|
203
|
+
|
204
|
+
# If no explicit parameters, try to get from existing global agent
|
205
|
+
if not platform_name:
|
206
|
+
agent = get_agent(current_agent_name)
|
207
|
+
if (
|
208
|
+
agent
|
209
|
+
and hasattr(agent, "model")
|
210
|
+
and getattr(agent, "model", None)
|
211
|
+
):
|
212
|
+
platform_name = agent.model.platform_name()
|
213
|
+
model_name = agent.model.name()
|
214
|
+
if not model_group and hasattr(agent.model, "model_group"):
|
215
|
+
model_group = agent.model.model_group
|
190
216
|
|
191
217
|
# Create a new platform instance
|
192
218
|
if platform_name:
|
jarvis/jarvis_platform/base.py
CHANGED
@@ -188,12 +188,11 @@ class BasePlatform(ABC):
|
|
188
188
|
|
189
189
|
# If content overflows, truncate to show only the last few lines
|
190
190
|
if len(lines) > max_text_height:
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
]
|
191
|
+
# Rebuild the text from the wrapped lines to ensure visual consistency
|
192
|
+
# This correctly handles both wrapped long lines and explicit newlines
|
193
|
+
text_content.plain = "\n".join(
|
194
|
+
[line.plain for line in lines[-max_text_height:]]
|
195
195
|
)
|
196
|
-
text_content.plain = new_text
|
197
196
|
|
198
197
|
panel.subtitle = (
|
199
198
|
"[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
|
@@ -18,6 +18,7 @@ from jarvis.jarvis_utils.input import get_multiline_input, get_single_line_input
|
|
18
18
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
19
19
|
from jarvis.jarvis_utils.utils import init_env
|
20
20
|
from jarvis.jarvis_platform_manager.service import start_service
|
21
|
+
from jarvis.jarvis_utils.fzf import fzf_select
|
21
22
|
|
22
23
|
app = typer.Typer(help="Jarvis AI 平台")
|
23
24
|
|
@@ -451,17 +452,33 @@ def role_command(
|
|
451
452
|
)
|
452
453
|
PrettyOutput.print(output_str, OutputType.INFO)
|
453
454
|
|
454
|
-
#
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
455
|
+
# 让用户选择角色(优先 fzf,回退编号输入)
|
456
|
+
selected_role = None # type: ignore[var-annotated]
|
457
|
+
fzf_options = [
|
458
|
+
f"{i:>3} | {role['name']} - {role.get('description', '')}"
|
459
|
+
for i, role in enumerate(config["roles"], 1)
|
460
|
+
]
|
461
|
+
selected_str = fzf_select(fzf_options, prompt="选择角色编号 (Enter退出) > ")
|
462
|
+
if selected_str:
|
463
|
+
try:
|
464
|
+
num_part = selected_str.split("|", 1)[0].strip()
|
465
|
+
idx = int(num_part)
|
466
|
+
if 1 <= idx <= len(config["roles"]):
|
467
|
+
selected_role = config["roles"][idx - 1]
|
468
|
+
except Exception:
|
469
|
+
selected_role = None
|
470
|
+
|
471
|
+
if selected_role is None:
|
472
|
+
raw_choice = get_single_line_input("请选择角色(输入编号,直接回车退出): ")
|
473
|
+
if not raw_choice.strip():
|
474
|
+
PrettyOutput.print("已取消,退出程序", OutputType.INFO)
|
475
|
+
raise typer.Exit(code=0)
|
476
|
+
try:
|
477
|
+
choice = int(raw_choice)
|
478
|
+
selected_role = config["roles"][choice - 1]
|
479
|
+
except (ValueError, IndexError):
|
480
|
+
PrettyOutput.print("无效的选择", OutputType.ERROR)
|
481
|
+
return
|
465
482
|
|
466
483
|
|
467
484
|
|
jarvis/jarvis_stats/cli.py
CHANGED
@@ -98,11 +98,6 @@ def inc(
|
|
98
98
|
@app.command()
|
99
99
|
def show(
|
100
100
|
metric: Optional[str] = typer.Argument(None, help="指标名称,不指定则显示所有"),
|
101
|
-
last_hours: Optional[int] = typer.Option(None, "--hours", "-h", help="最近N小时"),
|
102
|
-
last_days: Optional[int] = typer.Option(None, "--days", "-d", help="最近N天"),
|
103
|
-
format: str = typer.Option(
|
104
|
-
"table", "--format", "-f", help="显示格式: table/chart/summary"
|
105
|
-
),
|
106
101
|
aggregation: str = typer.Option(
|
107
102
|
"hourly", "--agg", "-a", help="聚合方式: hourly/daily"
|
108
103
|
),
|
@@ -123,9 +118,7 @@ def show(
|
|
123
118
|
|
124
119
|
stats.show(
|
125
120
|
metric_name=metric,
|
126
|
-
|
127
|
-
last_days=last_days,
|
128
|
-
format=format,
|
121
|
+
|
129
122
|
aggregation=aggregation,
|
130
123
|
tags=tag_dict if tag_dict else None,
|
131
124
|
)
|
@@ -136,8 +129,6 @@ def plot(
|
|
136
129
|
metric: Optional[str] = typer.Argument(
|
137
130
|
None, help="指标名称(可选,不指定则根据标签过滤所有匹配的指标)"
|
138
131
|
),
|
139
|
-
last_hours: Optional[int] = typer.Option(None, "--hours", "-h", help="最近N小时"),
|
140
|
-
last_days: Optional[int] = typer.Option(None, "--days", "-d", help="最近N天"),
|
141
132
|
aggregation: str = typer.Option(
|
142
133
|
"hourly", "--agg", "-a", help="聚合方式: hourly/daily"
|
143
134
|
),
|
@@ -160,8 +151,6 @@ def plot(
|
|
160
151
|
|
161
152
|
stats.plot(
|
162
153
|
metric_name=metric,
|
163
|
-
last_hours=last_hours,
|
164
|
-
last_days=last_days,
|
165
154
|
aggregation=aggregation,
|
166
155
|
width=width,
|
167
156
|
height=height,
|
@@ -260,9 +249,9 @@ def clean(
|
|
260
249
|
@app.command()
|
261
250
|
def export(
|
262
251
|
metric: str = typer.Argument(..., help="指标名称"),
|
263
|
-
|
264
|
-
|
265
|
-
|
252
|
+
|
253
|
+
|
254
|
+
|
266
255
|
tags: Optional[List[str]] = typer.Option(
|
267
256
|
None, "--tag", "-t", help="标签过滤,格式: key=value"
|
268
257
|
),
|
@@ -285,27 +274,19 @@ def export(
|
|
285
274
|
# 获取数据
|
286
275
|
data = stats.get_stats(
|
287
276
|
metric_name=metric,
|
288
|
-
last_hours=last_hours,
|
289
|
-
last_days=last_days,
|
290
277
|
tags=tag_dict if tag_dict else None,
|
291
278
|
)
|
292
279
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
)
|
280
|
+
# CSV格式输出(默认)
|
281
|
+
records = data.get("records", [])
|
282
|
+
if records:
|
283
|
+
writer = csv.writer(sys.stdout)
|
284
|
+
writer.writerow(["timestamp", "value", "tags"])
|
285
|
+
for record in records:
|
286
|
+
tags_str = json.dumps(record.get("tags", {}))
|
287
|
+
writer.writerow([record["timestamp"], record["value"], tags_str])
|
298
288
|
else:
|
299
|
-
|
300
|
-
records = data.get("records", [])
|
301
|
-
if records:
|
302
|
-
writer = csv.writer(sys.stdout)
|
303
|
-
writer.writerow(["timestamp", "value", "tags"])
|
304
|
-
for record in records:
|
305
|
-
tags_str = json.dumps(record.get("tags", {}))
|
306
|
-
writer.writerow([record["timestamp"], record["value"], tags_str])
|
307
|
-
else:
|
308
|
-
rprint("[yellow]没有找到数据[/yellow]", file=sys.stderr)
|
289
|
+
rprint("[yellow]没有找到数据[/yellow]", file=sys.stderr)
|
309
290
|
|
310
291
|
|
311
292
|
@app.command()
|