jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.0__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 +289 -87
- jarvis/jarvis_agent/agent_manager.py +17 -8
- jarvis/jarvis_agent/edit_file_handler.py +374 -86
- jarvis/jarvis_agent/event_bus.py +1 -1
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/jarvis.py +601 -43
- jarvis/jarvis_agent/main.py +32 -2
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +38 -5
- jarvis/jarvis_agent/share_manager.py +8 -1
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +5 -2
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1171 -94
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +270 -8
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/code_review.py +483 -569
- jarvis/jarvis_data/config_schema.json +97 -8
- jarvis/jarvis_git_utils/git_commiter.py +38 -26
- jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
- jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
- jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
- jarvis/jarvis_multi_agent/__init__.py +239 -25
- jarvis/jarvis_multi_agent/main.py +37 -1
- jarvis/jarvis_platform/base.py +103 -51
- jarvis/jarvis_platform/openai.py +26 -1
- jarvis/jarvis_platform/yuanbao.py +1 -1
- jarvis/jarvis_platform_manager/service.py +2 -2
- jarvis/jarvis_rag/cli.py +4 -4
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_stats/cli.py +1 -1
- jarvis/jarvis_stats/stats.py +1 -1
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/cli/main.py +1 -0
- jarvis/jarvis_tools/execute_script.py +46 -9
- jarvis/jarvis_tools/generate_new_tool.py +3 -1
- jarvis/jarvis_tools/read_code.py +275 -12
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +5 -3
- jarvis/jarvis_tools/registry.py +73 -35
- jarvis/jarvis_tools/search_web.py +15 -11
- jarvis/jarvis_tools/sub_agent.py +24 -42
- jarvis/jarvis_tools/sub_code_agent.py +14 -13
- jarvis/jarvis_tools/virtual_tty.py +1 -1
- jarvis/jarvis_utils/config.py +187 -35
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/git_utils.py +181 -6
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +78 -2
- jarvis/jarvis_utils/methodology.py +25 -19
- jarvis/jarvis_utils/utils.py +644 -359
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_tools/edit_file.py +0 -179
- jarvis/jarvis_tools/rewrite_file.py +0 -191
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_agent/jarvis.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""Jarvis AI 助手主入口模块"""
|
|
3
3
|
from typing import Optional, List
|
|
4
|
+
import shutil
|
|
5
|
+
from datetime import datetime
|
|
4
6
|
|
|
5
7
|
import typer
|
|
6
8
|
|
|
@@ -16,6 +18,9 @@ from jarvis.jarvis_utils.config import (
|
|
|
16
18
|
get_agent_definition_dirs,
|
|
17
19
|
get_multi_agent_dirs,
|
|
18
20
|
get_roles_dirs,
|
|
21
|
+
get_data_dir,
|
|
22
|
+
set_config,
|
|
23
|
+
is_non_interactive,
|
|
19
24
|
)
|
|
20
25
|
import jarvis.jarvis_utils.utils as jutils
|
|
21
26
|
from jarvis.jarvis_utils.input import user_confirm, get_single_line_input
|
|
@@ -23,10 +28,37 @@ from jarvis.jarvis_utils.fzf import fzf_select
|
|
|
23
28
|
import os
|
|
24
29
|
import subprocess
|
|
25
30
|
from pathlib import Path
|
|
31
|
+
import signal
|
|
26
32
|
import yaml # type: ignore
|
|
27
33
|
from rich.table import Table
|
|
28
34
|
from rich.console import Console
|
|
29
35
|
|
|
36
|
+
import sys
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _normalize_backup_data_argv(argv: List[str]) -> None:
|
|
40
|
+
"""
|
|
41
|
+
兼容旧版 Click/Typer 对可选参数的解析差异:
|
|
42
|
+
若用户仅提供 --backup-data 而不跟参数,则在解析前注入默认目录。
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
i = 0
|
|
46
|
+
while i < len(argv):
|
|
47
|
+
tok = argv[i]
|
|
48
|
+
if tok == "--backup-data":
|
|
49
|
+
# 情况1:位于末尾,无参数
|
|
50
|
+
# 情况2:后续是下一个选项(以 '-' 开头),表示未提供参数
|
|
51
|
+
if i == len(argv) - 1 or (i + 1 < len(argv) and argv[i + 1].startswith("-")):
|
|
52
|
+
argv.insert(i + 1, "~/jarvis_backups")
|
|
53
|
+
i += 1 # 跳过我们插入的默认值,避免重复插入
|
|
54
|
+
i += 1
|
|
55
|
+
except Exception:
|
|
56
|
+
# 静默忽略任何异常,避免影响主流程
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_normalize_backup_data_argv(sys.argv)
|
|
61
|
+
|
|
30
62
|
app = typer.Typer(help="Jarvis AI 助手")
|
|
31
63
|
|
|
32
64
|
|
|
@@ -184,6 +216,92 @@ def handle_interactive_config_option(
|
|
|
184
216
|
return True
|
|
185
217
|
|
|
186
218
|
|
|
219
|
+
def handle_backup_option(backup_dir_path: Optional[str]) -> bool:
|
|
220
|
+
"""处理数据备份选项,返回是否已处理并需提前结束。"""
|
|
221
|
+
if backup_dir_path is None:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
init_env("", config_file=None)
|
|
225
|
+
data_dir = Path(get_data_dir())
|
|
226
|
+
if not data_dir.is_dir():
|
|
227
|
+
PrettyOutput.print(f"数据目录不存在: {data_dir}", OutputType.ERROR)
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
backup_dir_str = backup_dir_path if backup_dir_path.strip() else "~/jarvis_backups"
|
|
231
|
+
backup_dir = Path(os.path.expanduser(backup_dir_str))
|
|
232
|
+
backup_dir.mkdir(exist_ok=True)
|
|
233
|
+
|
|
234
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
235
|
+
backup_file_base = backup_dir / f"jarvis_data_{timestamp}"
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
archive_path = shutil.make_archive(
|
|
239
|
+
str(backup_file_base), "zip", root_dir=str(data_dir)
|
|
240
|
+
)
|
|
241
|
+
PrettyOutput.print(f"数据已成功备份到: {archive_path}", OutputType.SUCCESS)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
PrettyOutput.print(f"数据备份失败: {e}", OutputType.ERROR)
|
|
244
|
+
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def handle_restore_option(restore_path: Optional[str], config_file: Optional[str]) -> bool:
|
|
249
|
+
"""处理数据恢复选项,返回是否已处理并需提前结束。"""
|
|
250
|
+
if not restore_path:
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
restore_file = Path(os.path.expanduser(os.path.expandvars(restore_path)))
|
|
254
|
+
# 兼容 ~ 与环境变量,避免用户输入未展开路径导致找不到文件
|
|
255
|
+
if not restore_file.is_file():
|
|
256
|
+
PrettyOutput.print(f"指定的恢复文件不存在: {restore_file}", OutputType.ERROR)
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
# 在恢复数据时不要触发完整环境初始化,避免引导流程或网络请求
|
|
260
|
+
# 优先从配置文件解析 JARVIS_DATA_PATH,否则回退到默认数据目录
|
|
261
|
+
data_dir_str: Optional[str] = None
|
|
262
|
+
try:
|
|
263
|
+
if config_file:
|
|
264
|
+
cfg_path = Path(os.path.expanduser(os.path.expandvars(config_file)))
|
|
265
|
+
if cfg_path.is_file():
|
|
266
|
+
with open(cfg_path, "r", encoding="utf-8", errors="ignore") as cf:
|
|
267
|
+
cfg_data = yaml.safe_load(cf) or {}
|
|
268
|
+
if isinstance(cfg_data, dict):
|
|
269
|
+
val = cfg_data.get("JARVIS_DATA_PATH")
|
|
270
|
+
if isinstance(val, str) and val.strip():
|
|
271
|
+
data_dir_str = val.strip()
|
|
272
|
+
except Exception:
|
|
273
|
+
data_dir_str = None
|
|
274
|
+
|
|
275
|
+
if not data_dir_str:
|
|
276
|
+
data_dir_str = get_data_dir()
|
|
277
|
+
|
|
278
|
+
data_dir = Path(os.path.expanduser(os.path.expandvars(str(data_dir_str))))
|
|
279
|
+
|
|
280
|
+
if data_dir.exists():
|
|
281
|
+
if not user_confirm(
|
|
282
|
+
f"数据目录 '{data_dir}' 已存在,恢复操作将覆盖它。是否继续?", default=False
|
|
283
|
+
):
|
|
284
|
+
PrettyOutput.print("恢复操作已取消。", OutputType.INFO)
|
|
285
|
+
return True
|
|
286
|
+
try:
|
|
287
|
+
shutil.rmtree(data_dir)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
PrettyOutput.print(f"无法移除现有数据目录: {e}", OutputType.ERROR)
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
data_dir.mkdir(parents=True)
|
|
294
|
+
shutil.unpack_archive(str(restore_file), str(data_dir), "zip")
|
|
295
|
+
PrettyOutput.print(
|
|
296
|
+
f"数据已从 '{restore_path}' 成功恢复到 '{data_dir}'", OutputType.SUCCESS
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
PrettyOutput.print(f"数据恢复失败: {e}", OutputType.ERROR)
|
|
301
|
+
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
|
|
187
305
|
def preload_config_for_flags(config_file: Optional[str]) -> None:
|
|
188
306
|
"""预加载配置(仅用于读取功能开关),不会显示欢迎信息或影响后续 init_env。"""
|
|
189
307
|
try:
|
|
@@ -202,6 +320,9 @@ def try_switch_to_jca_if_git_repo(
|
|
|
202
320
|
task: Optional[str],
|
|
203
321
|
) -> None:
|
|
204
322
|
"""在初始化环境前检测Git仓库,并可选择自动切换到代码开发模式(jca)。"""
|
|
323
|
+
# 非交互模式下跳过代码模式切换提示与相关输出
|
|
324
|
+
if is_non_interactive():
|
|
325
|
+
return
|
|
205
326
|
if is_enable_git_repo_jca_switch():
|
|
206
327
|
try:
|
|
207
328
|
res = subprocess.run(
|
|
@@ -249,10 +370,30 @@ def handle_builtin_config_selector(
|
|
|
249
370
|
"""在进入默认通用代理前,列出内置配置供选择(agent/multi_agent/roles)。"""
|
|
250
371
|
if is_enable_builtin_config_selector():
|
|
251
372
|
try:
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
373
|
+
# 查找可用的 builtin 目录(支持多候选)
|
|
374
|
+
builtin_dirs: List[Path] = []
|
|
375
|
+
try:
|
|
376
|
+
ancestors = list(Path(__file__).resolve().parents)
|
|
377
|
+
for anc in ancestors[:8]:
|
|
378
|
+
p = anc / "builtin"
|
|
379
|
+
if p.exists():
|
|
380
|
+
builtin_dirs.append(p)
|
|
381
|
+
except Exception:
|
|
382
|
+
pass
|
|
383
|
+
# 去重,保留顺序
|
|
384
|
+
_seen = set()
|
|
385
|
+
_unique: List[Path] = []
|
|
386
|
+
for d in builtin_dirs:
|
|
387
|
+
try:
|
|
388
|
+
key = str(d.resolve())
|
|
389
|
+
except Exception:
|
|
390
|
+
key = str(d)
|
|
391
|
+
if key not in _seen:
|
|
392
|
+
_seen.add(key)
|
|
393
|
+
_unique.append(d)
|
|
394
|
+
builtin_dirs = _unique
|
|
395
|
+
# 向后兼容:保留第一个候选作为 builtin_root
|
|
396
|
+
builtin_root = builtin_dirs[0] if builtin_dirs else None # type: ignore[assignment]
|
|
256
397
|
|
|
257
398
|
categories = [
|
|
258
399
|
("agent", "jarvis-agent", "*.yaml"),
|
|
@@ -293,8 +434,14 @@ def handle_builtin_config_selector(
|
|
|
293
434
|
# 忽略配置读取异常
|
|
294
435
|
pass
|
|
295
436
|
|
|
296
|
-
#
|
|
297
|
-
|
|
437
|
+
# 追加内置目录(支持多个候选)
|
|
438
|
+
try:
|
|
439
|
+
candidates = builtin_dirs if isinstance(builtin_dirs, list) and builtin_dirs else ([builtin_root] if builtin_root else [])
|
|
440
|
+
except Exception:
|
|
441
|
+
candidates = ([builtin_root] if builtin_root else [])
|
|
442
|
+
for _bd in candidates:
|
|
443
|
+
if _bd:
|
|
444
|
+
search_dirs.append(Path(_bd) / cat)
|
|
298
445
|
|
|
299
446
|
# 去重并保留顺序
|
|
300
447
|
unique_dirs = []
|
|
@@ -308,11 +455,14 @@ def handle_builtin_config_selector(
|
|
|
308
455
|
seen.add(key)
|
|
309
456
|
unique_dirs.append(Path(d))
|
|
310
457
|
|
|
311
|
-
#
|
|
458
|
+
# 可选调试输出:查看每类的搜索目录
|
|
312
459
|
try:
|
|
313
|
-
|
|
460
|
+
if os.environ.get("JARVIS_DEBUG_BUILTIN_SELECTOR") == "1":
|
|
461
|
+
PrettyOutput.print(
|
|
462
|
+
f"DEBUG: category={cat} search_dirs=" + ", ".join(str(p) for p in unique_dirs),
|
|
463
|
+
OutputType.INFO,
|
|
464
|
+
)
|
|
314
465
|
except Exception:
|
|
315
|
-
# 忽略更新过程中的所有异常,避免影响主流程
|
|
316
466
|
pass
|
|
317
467
|
|
|
318
468
|
for dir_path in unique_dirs:
|
|
@@ -374,6 +524,20 @@ def handle_builtin_config_selector(
|
|
|
374
524
|
)
|
|
375
525
|
|
|
376
526
|
if options:
|
|
527
|
+
# Add a default option to skip selection
|
|
528
|
+
options.insert(
|
|
529
|
+
0,
|
|
530
|
+
{
|
|
531
|
+
"category": "skip",
|
|
532
|
+
"cmd": "",
|
|
533
|
+
"file": "",
|
|
534
|
+
"name": "跳过选择 (使用默认通用代理)",
|
|
535
|
+
"desc": "直接按回车或ESC也可跳过",
|
|
536
|
+
"details": "",
|
|
537
|
+
"roles_count": 0,
|
|
538
|
+
},
|
|
539
|
+
)
|
|
540
|
+
|
|
377
541
|
PrettyOutput.section("可用的内置配置", OutputType.SUCCESS)
|
|
378
542
|
# 使用 rich Table 呈现
|
|
379
543
|
table = Table(show_header=True, header_style="bold magenta")
|
|
@@ -442,35 +606,47 @@ def handle_builtin_config_selector(
|
|
|
442
606
|
if choice_index != -1:
|
|
443
607
|
try:
|
|
444
608
|
sel = options[choice_index]
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
args
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
args
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
609
|
+
# If the "skip" option is chosen, do nothing and proceed to default agent
|
|
610
|
+
if sel["category"] == "skip":
|
|
611
|
+
pass
|
|
612
|
+
else:
|
|
613
|
+
args: List[str] = []
|
|
614
|
+
|
|
615
|
+
if sel["category"] == "agent":
|
|
616
|
+
# jarvis-agent 支持 -f/--config(全局配置)与 -c/--agent-definition
|
|
617
|
+
args = [str(sel["cmd"]), "-c", str(sel["file"])]
|
|
618
|
+
if model_group:
|
|
619
|
+
args += ["-g", str(model_group)]
|
|
620
|
+
if config_file:
|
|
621
|
+
args += ["-f", str(config_file)]
|
|
622
|
+
if task:
|
|
623
|
+
args += ["--task", str(task)]
|
|
624
|
+
|
|
625
|
+
elif sel["category"] == "multi_agent":
|
|
626
|
+
# jarvis-multi-agent 需要 -c/--config,用户输入通过 -i/--input 传递
|
|
627
|
+
# 同时传递 -g/--llm-group 以继承 jvs 的模型组选择
|
|
628
|
+
args = [str(sel["cmd"]), "-c", str(sel["file"])]
|
|
629
|
+
if model_group:
|
|
630
|
+
args += ["-g", str(model_group)]
|
|
631
|
+
if task:
|
|
632
|
+
args += ["-i", str(task)]
|
|
633
|
+
|
|
634
|
+
elif sel["category"] == "roles":
|
|
635
|
+
# jarvis-platform-manager role 子命令,支持 -c/-t/-g
|
|
636
|
+
args = [
|
|
637
|
+
str(sel["cmd"]),
|
|
638
|
+
"role",
|
|
639
|
+
"-c",
|
|
640
|
+
str(sel["file"]),
|
|
641
|
+
]
|
|
642
|
+
if model_group:
|
|
643
|
+
args += ["-g", str(model_group)]
|
|
644
|
+
|
|
645
|
+
if args:
|
|
646
|
+
PrettyOutput.print(
|
|
647
|
+
f"正在启动: {' '.join(args)}", OutputType.INFO
|
|
648
|
+
)
|
|
649
|
+
os.execvp(args[0], args)
|
|
474
650
|
except Exception:
|
|
475
651
|
# 任何异常都不影响默认流程
|
|
476
652
|
pass
|
|
@@ -521,6 +697,24 @@ def run_cli(
|
|
|
521
697
|
"--disable-methodology-analysis",
|
|
522
698
|
help="禁用方法论和任务分析(覆盖配置文件设置)",
|
|
523
699
|
),
|
|
700
|
+
backup_data: Optional[str] = typer.Option(
|
|
701
|
+
None,
|
|
702
|
+
"--backup-data",
|
|
703
|
+
help="备份 Jarvis 数据目录. 可选地传入备份目录. 默认为 '~/jarvis_backups'",
|
|
704
|
+
show_default=False,
|
|
705
|
+
flag_value="~/jarvis_backups",
|
|
706
|
+
),
|
|
707
|
+
restore_data: Optional[str] = typer.Option(
|
|
708
|
+
None, "--restore-data", help="从指定的压缩包恢复 Jarvis 数据"
|
|
709
|
+
),
|
|
710
|
+
non_interactive: bool = typer.Option(
|
|
711
|
+
False, "-n", "--non-interactive", help="启用非交互模式:用户无法与命令交互,脚本执行超时限制为5分钟"
|
|
712
|
+
),
|
|
713
|
+
plan: Optional[bool] = typer.Option(None, "--plan/--no-plan", help="启用或禁用任务规划(不指定则从配置加载)"),
|
|
714
|
+
web: bool = typer.Option(False, "--web", help="以 Web 模式启动,通过浏览器 WebSocket 交互"),
|
|
715
|
+
web_host: str = typer.Option("127.0.0.1", "--web-host", help="Web 服务主机"),
|
|
716
|
+
web_port: int = typer.Option(8765, "--web-port", help="Web 服务端口"),
|
|
717
|
+
stop: bool = typer.Option(False, "--stop", help="停止后台 Web 服务(需与 --web 一起使用)"),
|
|
524
718
|
) -> None:
|
|
525
719
|
"""Jarvis AI assistant command-line interface."""
|
|
526
720
|
if ctx.invoked_subcommand is not None:
|
|
@@ -529,6 +723,45 @@ def run_cli(
|
|
|
529
723
|
# 使用 rich 输出命令与快捷方式总览
|
|
530
724
|
print_commands_overview()
|
|
531
725
|
|
|
726
|
+
# CLI 标志:非交互模式(不依赖配置文件)
|
|
727
|
+
if non_interactive:
|
|
728
|
+
try:
|
|
729
|
+
os.environ["JARVIS_NON_INTERACTIVE"] = "true"
|
|
730
|
+
except Exception:
|
|
731
|
+
pass
|
|
732
|
+
# 注意:全局配置同步在 init_env 之后执行,避免被覆盖
|
|
733
|
+
|
|
734
|
+
# 同步其他 CLI 选项到全局配置,确保后续模块读取一致
|
|
735
|
+
try:
|
|
736
|
+
if model_group:
|
|
737
|
+
set_config("JARVIS_LLM_GROUP", str(model_group))
|
|
738
|
+
if tool_group:
|
|
739
|
+
set_config("JARVIS_TOOL_GROUP", str(tool_group))
|
|
740
|
+
if disable_methodology_analysis:
|
|
741
|
+
set_config("JARVIS_USE_METHODOLOGY", False)
|
|
742
|
+
set_config("JARVIS_USE_ANALYSIS", False)
|
|
743
|
+
if restore_session:
|
|
744
|
+
set_config("JARVIS_RESTORE_SESSION", True)
|
|
745
|
+
except Exception:
|
|
746
|
+
# 静默忽略同步异常,不影响主流程
|
|
747
|
+
pass
|
|
748
|
+
|
|
749
|
+
# 非交互模式要求从命令行传入任务
|
|
750
|
+
if non_interactive and not (task and str(task).strip()):
|
|
751
|
+
PrettyOutput.print(
|
|
752
|
+
"非交互模式已启用:必须使用 --task 传入任务内容,因多行输入不可用。",
|
|
753
|
+
OutputType.ERROR,
|
|
754
|
+
)
|
|
755
|
+
raise typer.Exit(code=2)
|
|
756
|
+
|
|
757
|
+
# 处理数据备份
|
|
758
|
+
if handle_backup_option(backup_data):
|
|
759
|
+
return
|
|
760
|
+
|
|
761
|
+
# 处理数据恢复
|
|
762
|
+
if handle_restore_option(restore_data, config_file):
|
|
763
|
+
return
|
|
764
|
+
|
|
532
765
|
# 处理配置文件编辑
|
|
533
766
|
if handle_edit_option(edit, config_file):
|
|
534
767
|
return
|
|
@@ -547,30 +780,355 @@ def run_cli(
|
|
|
547
780
|
|
|
548
781
|
# 预加载配置(仅用于读取功能开关),不会显示欢迎信息或影响后续 init_env
|
|
549
782
|
preload_config_for_flags(config_file)
|
|
783
|
+
# Web 模式后台管理:支持 --web 后台启动与 --web --stop 停止
|
|
784
|
+
if web:
|
|
785
|
+
# PID 文件路径(按端口区分,便于多实例)
|
|
786
|
+
pidfile = Path(os.path.expanduser("~/.jarvis")) / f"jarvis_web_{web_port}.pid"
|
|
787
|
+
# 停止后台服务
|
|
788
|
+
if stop:
|
|
789
|
+
try:
|
|
790
|
+
pf = pidfile
|
|
791
|
+
if not pf.exists():
|
|
792
|
+
# 兼容旧版本:回退检查数据目录中的旧 PID 文件位置
|
|
793
|
+
try:
|
|
794
|
+
pf_alt = Path(os.path.expanduser(os.path.expandvars(get_data_dir()))) / f"jarvis_web_{web_port}.pid"
|
|
795
|
+
except Exception:
|
|
796
|
+
pf_alt = None # type: ignore[assignment]
|
|
797
|
+
if pf_alt and pf_alt.exists(): # type: ignore[truthy-bool]
|
|
798
|
+
pf = pf_alt
|
|
799
|
+
if not pf.exists():
|
|
800
|
+
# 进一步回退:尝试按端口查找并停止(无 PID 文件)
|
|
801
|
+
killed_any = False
|
|
802
|
+
try:
|
|
803
|
+
res = subprocess.run(
|
|
804
|
+
["lsof", "-iTCP:%d" % web_port, "-sTCP:LISTEN", "-t"],
|
|
805
|
+
capture_output=True,
|
|
806
|
+
text=True,
|
|
807
|
+
)
|
|
808
|
+
if res.returncode == 0 and res.stdout.strip():
|
|
809
|
+
for ln in res.stdout.strip().splitlines():
|
|
810
|
+
try:
|
|
811
|
+
candidate_pid = int(ln.strip())
|
|
812
|
+
try:
|
|
813
|
+
os.kill(candidate_pid, signal.SIGTERM)
|
|
814
|
+
PrettyOutput.print(f"已按端口停止后台 Web 服务 (PID {candidate_pid})。", OutputType.SUCCESS)
|
|
815
|
+
killed_any = True
|
|
816
|
+
except Exception as e:
|
|
817
|
+
PrettyOutput.print(f"按端口停止失败: {e}", OutputType.WARNING)
|
|
818
|
+
except Exception:
|
|
819
|
+
continue
|
|
820
|
+
except Exception:
|
|
821
|
+
pass
|
|
822
|
+
if not killed_any:
|
|
823
|
+
try:
|
|
824
|
+
res2 = subprocess.run(["ss", "-ltpn"], capture_output=True, text=True)
|
|
825
|
+
if res2.returncode == 0 and res2.stdout:
|
|
826
|
+
for ln in res2.stdout.splitlines():
|
|
827
|
+
if f":{web_port} " in ln or f":{web_port}\n" in ln:
|
|
828
|
+
try:
|
|
829
|
+
idx = ln.find("pid=")
|
|
830
|
+
if idx != -1:
|
|
831
|
+
end = ln.find(",", idx)
|
|
832
|
+
pid_str2 = ln[idx+4:end if end != -1 else None]
|
|
833
|
+
candidate_pid = int(pid_str2)
|
|
834
|
+
try:
|
|
835
|
+
os.kill(candidate_pid, signal.SIGTERM)
|
|
836
|
+
PrettyOutput.print(f"已按端口停止后台 Web 服务 (PID {candidate_pid})。", OutputType.SUCCESS)
|
|
837
|
+
killed_any = True
|
|
838
|
+
except Exception as e:
|
|
839
|
+
PrettyOutput.print(f"按端口停止失败: {e}", OutputType.WARNING)
|
|
840
|
+
break
|
|
841
|
+
except Exception:
|
|
842
|
+
continue
|
|
843
|
+
except Exception:
|
|
844
|
+
pass
|
|
845
|
+
# 若仍未找到,扫描家目录下所有 Web PID 文件,尽力停止所有实例
|
|
846
|
+
if not killed_any:
|
|
847
|
+
try:
|
|
848
|
+
pid_dir = Path(os.path.expanduser("~/.jarvis"))
|
|
849
|
+
if pid_dir.is_dir():
|
|
850
|
+
for f in pid_dir.glob("jarvis_web_*.pid"):
|
|
851
|
+
try:
|
|
852
|
+
ptxt = f.read_text(encoding="utf-8").strip()
|
|
853
|
+
p = int(ptxt)
|
|
854
|
+
try:
|
|
855
|
+
os.kill(p, signal.SIGTERM)
|
|
856
|
+
PrettyOutput.print(f"已停止后台 Web 服务 (PID {p})。", OutputType.SUCCESS)
|
|
857
|
+
killed_any = True
|
|
858
|
+
except Exception as e:
|
|
859
|
+
PrettyOutput.print(f"停止 PID {p} 失败: {e}", OutputType.WARNING)
|
|
860
|
+
except Exception:
|
|
861
|
+
pass
|
|
862
|
+
try:
|
|
863
|
+
f.unlink(missing_ok=True)
|
|
864
|
+
except Exception:
|
|
865
|
+
pass
|
|
866
|
+
except Exception:
|
|
867
|
+
pass
|
|
868
|
+
if not killed_any:
|
|
869
|
+
PrettyOutput.print("未找到后台 Web 服务的 PID 文件,可能未启动或已停止。", OutputType.WARNING)
|
|
870
|
+
return
|
|
871
|
+
# 优先使用 PID 文件中的 PID
|
|
872
|
+
try:
|
|
873
|
+
pid_str = pf.read_text(encoding="utf-8").strip()
|
|
874
|
+
pid = int(pid_str)
|
|
875
|
+
except Exception:
|
|
876
|
+
pid = 0
|
|
877
|
+
killed = False
|
|
878
|
+
if pid > 0:
|
|
879
|
+
try:
|
|
880
|
+
os.kill(pid, signal.SIGTERM)
|
|
881
|
+
PrettyOutput.print(f"已向后台 Web 服务发送停止信号 (PID {pid})。", OutputType.SUCCESS)
|
|
882
|
+
killed = True
|
|
883
|
+
except Exception as e:
|
|
884
|
+
PrettyOutput.print(f"发送停止信号失败或进程不存在: {e}", OutputType.WARNING)
|
|
885
|
+
if not killed:
|
|
886
|
+
# 无 PID 文件或停止失败时,尝试按端口查找进程
|
|
887
|
+
candidate_pid = 0
|
|
888
|
+
try:
|
|
889
|
+
res = subprocess.run(
|
|
890
|
+
["lsof", "-iTCP:%d" % web_port, "-sTCP:LISTEN", "-t"],
|
|
891
|
+
capture_output=True,
|
|
892
|
+
text=True,
|
|
893
|
+
)
|
|
894
|
+
if res.returncode == 0 and res.stdout.strip():
|
|
895
|
+
for ln in res.stdout.strip().splitlines():
|
|
896
|
+
try:
|
|
897
|
+
candidate_pid = int(ln.strip())
|
|
898
|
+
break
|
|
899
|
+
except Exception:
|
|
900
|
+
continue
|
|
901
|
+
except Exception:
|
|
902
|
+
pass
|
|
903
|
+
if not candidate_pid:
|
|
904
|
+
try:
|
|
905
|
+
res2 = subprocess.run(["ss", "-ltpn"], capture_output=True, text=True)
|
|
906
|
+
if res2.returncode == 0 and res2.stdout:
|
|
907
|
+
for ln in res2.stdout.splitlines():
|
|
908
|
+
if f":{web_port} " in ln or f":{web_port}\n" in ln:
|
|
909
|
+
# 格式示例: LISTEN ... users:(("uvicorn",pid=12345,fd=7))
|
|
910
|
+
try:
|
|
911
|
+
idx = ln.find("pid=")
|
|
912
|
+
if idx != -1:
|
|
913
|
+
end = ln.find(",", idx)
|
|
914
|
+
pid_str2 = ln[idx+4:end if end != -1 else None]
|
|
915
|
+
candidate_pid = int(pid_str2)
|
|
916
|
+
break
|
|
917
|
+
except Exception:
|
|
918
|
+
continue
|
|
919
|
+
except Exception:
|
|
920
|
+
pass
|
|
921
|
+
if candidate_pid:
|
|
922
|
+
try:
|
|
923
|
+
os.kill(candidate_pid, signal.SIGTERM)
|
|
924
|
+
PrettyOutput.print(f"已按端口停止后台 Web 服务 (PID {candidate_pid})。", OutputType.SUCCESS)
|
|
925
|
+
killed = True
|
|
926
|
+
except Exception as e:
|
|
927
|
+
PrettyOutput.print(f"按端口停止失败: {e}", OutputType.WARNING)
|
|
928
|
+
# 清理可能存在的 PID 文件(两个位置)
|
|
929
|
+
try:
|
|
930
|
+
pidfile.unlink(missing_ok=True) # 家目录位置
|
|
931
|
+
except Exception:
|
|
932
|
+
pass
|
|
933
|
+
try:
|
|
934
|
+
alt_pf = Path(os.path.expanduser(os.path.expandvars(get_data_dir()))) / f"jarvis_web_{web_port}.pid"
|
|
935
|
+
alt_pf.unlink(missing_ok=True)
|
|
936
|
+
except Exception:
|
|
937
|
+
pass
|
|
938
|
+
except Exception as e:
|
|
939
|
+
PrettyOutput.print(f"停止后台 Web 服务失败: {e}", OutputType.ERROR)
|
|
940
|
+
finally:
|
|
941
|
+
return
|
|
942
|
+
# 后台启动:父进程拉起子进程并记录 PID
|
|
943
|
+
is_daemon = False
|
|
944
|
+
try:
|
|
945
|
+
is_daemon = os.environ.get("JARVIS_WEB_DAEMON") == "1"
|
|
946
|
+
except Exception:
|
|
947
|
+
is_daemon = False
|
|
948
|
+
if not is_daemon:
|
|
949
|
+
try:
|
|
950
|
+
# 构建子进程参数,传递关键配置
|
|
951
|
+
args = [
|
|
952
|
+
sys.executable,
|
|
953
|
+
"-m",
|
|
954
|
+
"jarvis.jarvis_agent.jarvis",
|
|
955
|
+
"--web",
|
|
956
|
+
"--web-host",
|
|
957
|
+
str(web_host),
|
|
958
|
+
"--web-port",
|
|
959
|
+
str(web_port),
|
|
960
|
+
]
|
|
961
|
+
if model_group:
|
|
962
|
+
args += ["-g", str(model_group)]
|
|
963
|
+
if tool_group:
|
|
964
|
+
args += ["-G", str(tool_group)]
|
|
965
|
+
if config_file:
|
|
966
|
+
args += ["-f", str(config_file)]
|
|
967
|
+
if restore_session:
|
|
968
|
+
args += ["--restore-session"]
|
|
969
|
+
if disable_methodology_analysis:
|
|
970
|
+
args += ["-D"]
|
|
971
|
+
if non_interactive:
|
|
972
|
+
args += ["-n"]
|
|
973
|
+
env = os.environ.copy()
|
|
974
|
+
env["JARVIS_WEB_DAEMON"] = "1"
|
|
975
|
+
# 启动子进程(后台运行)
|
|
976
|
+
proc = subprocess.Popen(
|
|
977
|
+
args,
|
|
978
|
+
env=env,
|
|
979
|
+
stdout=subprocess.DEVNULL,
|
|
980
|
+
stderr=subprocess.DEVNULL,
|
|
981
|
+
stdin=subprocess.DEVNULL,
|
|
982
|
+
close_fds=True,
|
|
983
|
+
)
|
|
984
|
+
# 记录 PID 到文件
|
|
985
|
+
try:
|
|
986
|
+
pidfile.parent.mkdir(parents=True, exist_ok=True)
|
|
987
|
+
except Exception:
|
|
988
|
+
pass
|
|
989
|
+
try:
|
|
990
|
+
pidfile.write_text(str(proc.pid), encoding="utf-8")
|
|
991
|
+
except Exception:
|
|
992
|
+
pass
|
|
993
|
+
PrettyOutput.print(
|
|
994
|
+
f"Web 服务已在后台启动 (PID {proc.pid}),地址: http://{web_host}:{web_port}",
|
|
995
|
+
OutputType.SUCCESS,
|
|
996
|
+
)
|
|
997
|
+
except Exception as e:
|
|
998
|
+
PrettyOutput.print(f"后台启动 Web 服务失败: {e}", OutputType.ERROR)
|
|
999
|
+
raise typer.Exit(code=1)
|
|
1000
|
+
return
|
|
550
1001
|
|
|
551
1002
|
# 在初始化环境前检测Git仓库,并可选择自动切换到代码开发模式(jca)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1003
|
+
if not non_interactive:
|
|
1004
|
+
try_switch_to_jca_if_git_repo(
|
|
1005
|
+
model_group, tool_group, config_file, restore_session, task
|
|
1006
|
+
)
|
|
555
1007
|
|
|
556
1008
|
# 在进入默认通用代理前,列出内置配置供选择(agent/multi_agent/roles)
|
|
557
|
-
|
|
1009
|
+
# 非交互模式下跳过内置角色/配置选择
|
|
1010
|
+
if not non_interactive:
|
|
1011
|
+
handle_builtin_config_selector(model_group, tool_group, config_file, task)
|
|
558
1012
|
|
|
559
1013
|
# 初始化环境
|
|
560
1014
|
init_env(
|
|
561
1015
|
"欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
|
|
562
1016
|
)
|
|
563
1017
|
|
|
1018
|
+
# 在初始化环境后同步 CLI 选项到全局配置,避免被 init_env 覆盖
|
|
1019
|
+
try:
|
|
1020
|
+
if model_group:
|
|
1021
|
+
set_config("JARVIS_LLM_GROUP", str(model_group))
|
|
1022
|
+
if tool_group:
|
|
1023
|
+
set_config("JARVIS_TOOL_GROUP", str(tool_group))
|
|
1024
|
+
if disable_methodology_analysis:
|
|
1025
|
+
set_config("JARVIS_USE_METHODOLOGY", False)
|
|
1026
|
+
set_config("JARVIS_USE_ANALYSIS", False)
|
|
1027
|
+
if restore_session:
|
|
1028
|
+
set_config("JARVIS_RESTORE_SESSION", True)
|
|
1029
|
+
if non_interactive:
|
|
1030
|
+
# 保持运行期非交互标志
|
|
1031
|
+
set_config("JARVIS_NON_INTERACTIVE", True)
|
|
1032
|
+
except Exception:
|
|
1033
|
+
# 静默忽略同步异常,不影响主流程
|
|
1034
|
+
pass
|
|
1035
|
+
|
|
564
1036
|
# 运行主流程
|
|
565
1037
|
try:
|
|
1038
|
+
# 在 Web 模式下注入基于 WebSocket 的输入/确认回调
|
|
1039
|
+
extra_kwargs = {}
|
|
1040
|
+
if web:
|
|
1041
|
+
# 纯 xterm 交互模式:不注入 WebBridge 的输入/确认回调,避免阻塞等待浏览器响应
|
|
1042
|
+
#(交互由 /terminal PTY 会话中的 jvs 进程处理)
|
|
1043
|
+
pass
|
|
1044
|
+
|
|
566
1045
|
agent_manager = AgentManager(
|
|
567
1046
|
model_group=model_group,
|
|
568
1047
|
tool_group=tool_group,
|
|
569
1048
|
restore_session=restore_session,
|
|
570
1049
|
use_methodology=False if disable_methodology_analysis else None,
|
|
571
1050
|
use_analysis=False if disable_methodology_analysis else None,
|
|
1051
|
+
non_interactive=non_interactive,
|
|
1052
|
+
**extra_kwargs,
|
|
572
1053
|
)
|
|
573
|
-
agent_manager.initialize()
|
|
1054
|
+
agent = agent_manager.initialize()
|
|
1055
|
+
# CLI 开关:启用/禁用规划(不依赖 AgentManager 支持,直接设置 Agent 属性)
|
|
1056
|
+
if plan is not None:
|
|
1057
|
+
try:
|
|
1058
|
+
agent.plan = bool(plan)
|
|
1059
|
+
except Exception:
|
|
1060
|
+
pass
|
|
1061
|
+
|
|
1062
|
+
if web:
|
|
1063
|
+
try:
|
|
1064
|
+
|
|
1065
|
+
from jarvis.jarvis_agent.web_server import start_web_server
|
|
1066
|
+
from jarvis.jarvis_agent.stdio_redirect import enable_web_stdio_redirect, enable_web_stdin_redirect
|
|
1067
|
+
# 在 Web 模式下固定TTY宽度为200,改善前端显示效果
|
|
1068
|
+
try:
|
|
1069
|
+
import os as _os
|
|
1070
|
+
_os.environ["COLUMNS"] = "200"
|
|
1071
|
+
# 尝试固定全局 Console 的宽度(PrettyOutput 使用该 Console 实例)
|
|
1072
|
+
try:
|
|
1073
|
+
from jarvis.jarvis_utils.globals import console as _console
|
|
1074
|
+
try:
|
|
1075
|
+
_console._width = 200 # rich Console的固定宽度参数
|
|
1076
|
+
except Exception:
|
|
1077
|
+
pass
|
|
1078
|
+
except Exception:
|
|
1079
|
+
pass
|
|
1080
|
+
except Exception:
|
|
1081
|
+
pass
|
|
1082
|
+
# 使用 STDIO 重定向,取消 Sink 广播以避免重复输出
|
|
1083
|
+
# 启用标准输出/错误的WebSocket重定向(捕获工具直接打印的输出)
|
|
1084
|
+
enable_web_stdio_redirect()
|
|
1085
|
+
# 启用来自前端 xterm 的 STDIN 重定向,使交互式命令可从浏览器获取输入
|
|
1086
|
+
try:
|
|
1087
|
+
enable_web_stdin_redirect()
|
|
1088
|
+
except Exception:
|
|
1089
|
+
pass
|
|
1090
|
+
# 记录用于交互式终端(PTY)重启的 jvs 启动命令(移除 web 相关参数)
|
|
1091
|
+
try:
|
|
1092
|
+
import sys as _sys
|
|
1093
|
+
import os as _os
|
|
1094
|
+
import json as _json
|
|
1095
|
+
_argv = list(_sys.argv)
|
|
1096
|
+
# 去掉程序名(argv[0]),并过滤 --web 相关参数
|
|
1097
|
+
filtered = []
|
|
1098
|
+
i = 1
|
|
1099
|
+
while i < len(_argv):
|
|
1100
|
+
a = _argv[i]
|
|
1101
|
+
if a == "--web" or a.startswith("--web="):
|
|
1102
|
+
i += 1
|
|
1103
|
+
continue
|
|
1104
|
+
if a == "--web-host":
|
|
1105
|
+
i += 2
|
|
1106
|
+
continue
|
|
1107
|
+
if a.startswith("--web-host="):
|
|
1108
|
+
i += 1
|
|
1109
|
+
continue
|
|
1110
|
+
if a == "--web-port":
|
|
1111
|
+
i += 2
|
|
1112
|
+
continue
|
|
1113
|
+
if a.startswith("--web-port="):
|
|
1114
|
+
i += 1
|
|
1115
|
+
continue
|
|
1116
|
+
filtered.append(a)
|
|
1117
|
+
i += 1
|
|
1118
|
+
# 使用 jvs 命令作为可执行文件,保留其余业务参数
|
|
1119
|
+
cmd = ["jvs"] + filtered
|
|
1120
|
+
_os.environ["JARVIS_WEB_LAUNCH_JSON"] = _json.dumps(cmd, ensure_ascii=False)
|
|
1121
|
+
except Exception:
|
|
1122
|
+
pass
|
|
1123
|
+
PrettyOutput.print("以 Web 模式启动,请在浏览器中打开提供的地址进行交互。", OutputType.INFO)
|
|
1124
|
+
# 启动 Web 服务(阻塞调用)
|
|
1125
|
+
start_web_server(agent_manager, host=web_host, port=web_port)
|
|
1126
|
+
return
|
|
1127
|
+
except Exception as e:
|
|
1128
|
+
PrettyOutput.print(f"Web 模式启动失败: {e}", OutputType.ERROR)
|
|
1129
|
+
raise typer.Exit(code=1)
|
|
1130
|
+
|
|
1131
|
+
# 默认 CLI 模式:运行任务(可能来自 --task 或交互输入)
|
|
574
1132
|
agent_manager.run_task(task)
|
|
575
1133
|
except typer.Exit:
|
|
576
1134
|
raise
|