jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +458 -152
  3. jarvis/jarvis_agent/agent_manager.py +17 -13
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +329 -0
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +628 -55
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +34 -10
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +105 -9
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +20 -22
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  31. jarvis/jarvis_agent/task_analyzer.py +31 -6
  32. jarvis/jarvis_agent/task_manager.py +11 -27
  33. jarvis/jarvis_agent/tool_executor.py +2 -3
  34. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  35. jarvis/jarvis_agent/utils.py +5 -1
  36. jarvis/jarvis_agent/web_bridge.py +189 -0
  37. jarvis/jarvis_agent/web_output_sink.py +53 -0
  38. jarvis/jarvis_agent/web_server.py +786 -0
  39. jarvis/jarvis_c2rust/__init__.py +26 -0
  40. jarvis/jarvis_c2rust/cli.py +575 -0
  41. jarvis/jarvis_c2rust/collector.py +250 -0
  42. jarvis/jarvis_c2rust/constants.py +26 -0
  43. jarvis/jarvis_c2rust/library_replacer.py +1254 -0
  44. jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
  45. jarvis/jarvis_c2rust/loaders.py +207 -0
  46. jarvis/jarvis_c2rust/models.py +28 -0
  47. jarvis/jarvis_c2rust/optimizer.py +2157 -0
  48. jarvis/jarvis_c2rust/scanner.py +1681 -0
  49. jarvis/jarvis_c2rust/transpiler.py +2983 -0
  50. jarvis/jarvis_c2rust/utils.py +385 -0
  51. jarvis/jarvis_code_agent/build_validation_config.py +132 -0
  52. jarvis/jarvis_code_agent/code_agent.py +1371 -220
  53. jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
  54. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
  60. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
  61. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
  62. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
  63. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
  64. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
  65. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
  66. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
  67. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
  68. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  69. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
  70. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  71. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  72. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  73. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  74. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  75. jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
  76. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
  77. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
  78. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
  79. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  80. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  81. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
  82. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
  83. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  84. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
  85. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  86. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
  87. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
  88. jarvis/jarvis_code_agent/lint.py +501 -8
  89. jarvis/jarvis_code_agent/utils.py +141 -0
  90. jarvis/jarvis_code_analysis/code_review.py +493 -584
  91. jarvis/jarvis_data/config_schema.json +128 -12
  92. jarvis/jarvis_git_squash/main.py +4 -5
  93. jarvis/jarvis_git_utils/git_commiter.py +82 -75
  94. jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
  95. jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
  96. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  97. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  98. jarvis/jarvis_methodology/main.py +32 -48
  99. jarvis/jarvis_multi_agent/__init__.py +287 -55
  100. jarvis/jarvis_multi_agent/main.py +36 -4
  101. jarvis/jarvis_platform/base.py +524 -202
  102. jarvis/jarvis_platform/human.py +7 -8
  103. jarvis/jarvis_platform/kimi.py +30 -36
  104. jarvis/jarvis_platform/openai.py +88 -25
  105. jarvis/jarvis_platform/registry.py +26 -10
  106. jarvis/jarvis_platform/tongyi.py +24 -25
  107. jarvis/jarvis_platform/yuanbao.py +32 -43
  108. jarvis/jarvis_platform_manager/main.py +66 -77
  109. jarvis/jarvis_platform_manager/service.py +8 -13
  110. jarvis/jarvis_rag/cli.py +53 -55
  111. jarvis/jarvis_rag/embedding_manager.py +13 -18
  112. jarvis/jarvis_rag/llm_interface.py +8 -9
  113. jarvis/jarvis_rag/query_rewriter.py +10 -21
  114. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  115. jarvis/jarvis_rag/reranker.py +4 -5
  116. jarvis/jarvis_rag/retriever.py +28 -30
  117. jarvis/jarvis_sec/__init__.py +305 -0
  118. jarvis/jarvis_sec/agents.py +143 -0
  119. jarvis/jarvis_sec/analysis.py +276 -0
  120. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  121. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  122. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  123. jarvis/jarvis_sec/cli.py +139 -0
  124. jarvis/jarvis_sec/clustering.py +1439 -0
  125. jarvis/jarvis_sec/file_manager.py +427 -0
  126. jarvis/jarvis_sec/parsers.py +73 -0
  127. jarvis/jarvis_sec/prompts.py +268 -0
  128. jarvis/jarvis_sec/report.py +336 -0
  129. jarvis/jarvis_sec/review.py +453 -0
  130. jarvis/jarvis_sec/status.py +264 -0
  131. jarvis/jarvis_sec/types.py +20 -0
  132. jarvis/jarvis_sec/utils.py +499 -0
  133. jarvis/jarvis_sec/verification.py +848 -0
  134. jarvis/jarvis_sec/workflow.py +226 -0
  135. jarvis/jarvis_smart_shell/main.py +38 -87
  136. jarvis/jarvis_stats/cli.py +2 -2
  137. jarvis/jarvis_stats/stats.py +8 -8
  138. jarvis/jarvis_stats/storage.py +15 -21
  139. jarvis/jarvis_stats/visualizer.py +1 -1
  140. jarvis/jarvis_tools/clear_memory.py +3 -20
  141. jarvis/jarvis_tools/cli/main.py +21 -23
  142. jarvis/jarvis_tools/edit_file.py +1019 -132
  143. jarvis/jarvis_tools/execute_script.py +83 -25
  144. jarvis/jarvis_tools/file_analyzer.py +6 -9
  145. jarvis/jarvis_tools/generate_new_tool.py +14 -21
  146. jarvis/jarvis_tools/lsp_client.py +1552 -0
  147. jarvis/jarvis_tools/methodology.py +2 -3
  148. jarvis/jarvis_tools/read_code.py +1736 -35
  149. jarvis/jarvis_tools/read_symbols.py +140 -0
  150. jarvis/jarvis_tools/read_webpage.py +12 -13
  151. jarvis/jarvis_tools/registry.py +427 -200
  152. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  153. jarvis/jarvis_tools/rewrite_file.py +72 -158
  154. jarvis/jarvis_tools/save_memory.py +3 -15
  155. jarvis/jarvis_tools/search_web.py +18 -18
  156. jarvis/jarvis_tools/sub_agent.py +36 -43
  157. jarvis/jarvis_tools/sub_code_agent.py +25 -26
  158. jarvis/jarvis_tools/virtual_tty.py +55 -33
  159. jarvis/jarvis_utils/clipboard.py +7 -10
  160. jarvis/jarvis_utils/config.py +232 -45
  161. jarvis/jarvis_utils/embedding.py +8 -5
  162. jarvis/jarvis_utils/fzf.py +8 -8
  163. jarvis/jarvis_utils/git_utils.py +225 -36
  164. jarvis/jarvis_utils/globals.py +3 -3
  165. jarvis/jarvis_utils/http.py +1 -1
  166. jarvis/jarvis_utils/input.py +99 -48
  167. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  168. jarvis/jarvis_utils/methodology.py +52 -48
  169. jarvis/jarvis_utils/utils.py +819 -491
  170. jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
  171. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  172. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
  173. jarvis/jarvis_agent/config.py +0 -92
  174. jarvis/jarvis_agent/edit_file_handler.py +0 -296
  175. jarvis/jarvis_platform/ai8.py +0 -332
  176. jarvis/jarvis_tools/ask_user.py +0 -54
  177. jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
  178. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  179. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  180. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  181. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,11 @@
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
 
7
- from jarvis.jarvis_agent import OutputType, PrettyOutput
8
9
  from jarvis.jarvis_agent.agent_manager import AgentManager
9
10
  from jarvis.jarvis_agent.config_editor import ConfigEditor
10
11
  from jarvis.jarvis_agent.methodology_share_manager import MethodologyShareManager
@@ -16,6 +17,9 @@ from jarvis.jarvis_utils.config import (
16
17
  get_agent_definition_dirs,
17
18
  get_multi_agent_dirs,
18
19
  get_roles_dirs,
20
+ get_data_dir,
21
+ set_config,
22
+ is_non_interactive,
19
23
  )
20
24
  import jarvis.jarvis_utils.utils as jutils
21
25
  from jarvis.jarvis_utils.input import user_confirm, get_single_line_input
@@ -23,10 +27,37 @@ from jarvis.jarvis_utils.fzf import fzf_select
23
27
  import os
24
28
  import subprocess
25
29
  from pathlib import Path
30
+ import signal
26
31
  import yaml # type: ignore
27
32
  from rich.table import Table
28
33
  from rich.console import Console
29
34
 
35
+ import sys
36
+
37
+
38
+ def _normalize_backup_data_argv(argv: List[str]) -> None:
39
+ """
40
+ 兼容旧版 Click/Typer 对可选参数的解析差异:
41
+ 若用户仅提供 --backup-data 而不跟参数,则在解析前注入默认目录。
42
+ """
43
+ try:
44
+ i = 0
45
+ while i < len(argv):
46
+ tok = argv[i]
47
+ if tok == "--backup-data":
48
+ # 情况1:位于末尾,无参数
49
+ # 情况2:后续是下一个选项(以 '-' 开头),表示未提供参数
50
+ if i == len(argv) - 1 or (i + 1 < len(argv) and argv[i + 1].startswith("-")):
51
+ argv.insert(i + 1, "~/jarvis_backups")
52
+ i += 1 # 跳过我们插入的默认值,避免重复插入
53
+ i += 1
54
+ except Exception:
55
+ # 静默忽略任何异常,避免影响主流程
56
+ pass
57
+
58
+
59
+ _normalize_backup_data_argv(sys.argv)
60
+
30
61
  app = typer.Typer(help="Jarvis AI 助手")
31
62
 
32
63
 
@@ -138,7 +169,7 @@ def handle_interactive_config_option(
138
169
  config_data, ask_all=True
139
170
  )
140
171
  if not changed:
141
- PrettyOutput.print("没有需要更新的配置项,保持现有配置。", OutputType.INFO)
172
+ print("ℹ️ 没有需要更新的配置项,保持现有配置。")
142
173
  return True
143
174
 
144
175
  # 剔除与 schema 默认值一致的键,保持配置精简
@@ -177,13 +208,97 @@ def handle_interactive_config_option(
177
208
  wf.write(header)
178
209
  wf.write(yaml_str)
179
210
 
180
- PrettyOutput.print(f"配置已更新: {config_path}", OutputType.SUCCESS)
211
+ print(f"配置已更新: {config_path}")
181
212
  return True
182
213
  except Exception as e:
183
- PrettyOutput.print(f"交互式配置失败: {e}", OutputType.ERROR)
214
+ print(f"交互式配置失败: {e}")
184
215
  return True
185
216
 
186
217
 
218
+ def handle_backup_option(backup_dir_path: Optional[str]) -> bool:
219
+ """处理数据备份选项,返回是否已处理并需提前结束。"""
220
+ if backup_dir_path is None:
221
+ return False
222
+
223
+ init_env("", config_file=None)
224
+ data_dir = Path(get_data_dir())
225
+ if not data_dir.is_dir():
226
+ print(f"❌ 数据目录不存在: {data_dir}")
227
+ return True
228
+
229
+ backup_dir_str = backup_dir_path if backup_dir_path.strip() else "~/jarvis_backups"
230
+ backup_dir = Path(os.path.expanduser(backup_dir_str))
231
+ backup_dir.mkdir(exist_ok=True)
232
+
233
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
234
+ backup_file_base = backup_dir / f"jarvis_data_{timestamp}"
235
+
236
+ try:
237
+ archive_path = shutil.make_archive(
238
+ str(backup_file_base), "zip", root_dir=str(data_dir)
239
+ )
240
+ print(f"✅ 数据已成功备份到: {archive_path}")
241
+ except Exception as e:
242
+ print(f"❌ 数据备份失败: {e}")
243
+
244
+ return True
245
+
246
+
247
+ def handle_restore_option(restore_path: Optional[str], config_file: Optional[str]) -> bool:
248
+ """处理数据恢复选项,返回是否已处理并需提前结束。"""
249
+ if not restore_path:
250
+ return False
251
+
252
+ restore_file = Path(os.path.expanduser(os.path.expandvars(restore_path)))
253
+ # 兼容 ~ 与环境变量,避免用户输入未展开路径导致找不到文件
254
+ if not restore_file.is_file():
255
+ print(f"❌ 指定的恢复文件不存在: {restore_file}")
256
+ return True
257
+
258
+ # 在恢复数据时不要触发完整环境初始化,避免引导流程或网络请求
259
+ # 优先从配置文件解析 JARVIS_DATA_PATH,否则回退到默认数据目录
260
+ data_dir_str: Optional[str] = None
261
+ try:
262
+ if config_file:
263
+ cfg_path = Path(os.path.expanduser(os.path.expandvars(config_file)))
264
+ if cfg_path.is_file():
265
+ with open(cfg_path, "r", encoding="utf-8", errors="ignore") as cf:
266
+ cfg_data = yaml.safe_load(cf) or {}
267
+ if isinstance(cfg_data, dict):
268
+ val = cfg_data.get("JARVIS_DATA_PATH")
269
+ if isinstance(val, str) and val.strip():
270
+ data_dir_str = val.strip()
271
+ except Exception:
272
+ data_dir_str = None
273
+
274
+ if not data_dir_str:
275
+ data_dir_str = get_data_dir()
276
+
277
+ data_dir = Path(os.path.expanduser(os.path.expandvars(str(data_dir_str))))
278
+
279
+ if data_dir.exists():
280
+ if not user_confirm(
281
+ f"数据目录 '{data_dir}' 已存在,恢复操作将覆盖它。是否继续?", default=False
282
+ ):
283
+ print("ℹ️ 恢复操作已取消。")
284
+ return True
285
+ try:
286
+ shutil.rmtree(data_dir)
287
+ except Exception as e:
288
+ print(f"❌ 无法移除现有数据目录: {e}")
289
+ return True
290
+
291
+ try:
292
+ data_dir.mkdir(parents=True)
293
+ shutil.unpack_archive(str(restore_file), str(data_dir), "zip")
294
+ print(f"✅ 数据已从 '{restore_path}' 成功恢复到 '{data_dir}'")
295
+
296
+ except Exception as e:
297
+ print(f"❌ 数据恢复失败: {e}")
298
+
299
+ return True
300
+
301
+
187
302
  def preload_config_for_flags(config_file: Optional[str]) -> None:
188
303
  """预加载配置(仅用于读取功能开关),不会显示欢迎信息或影响后续 init_env。"""
189
304
  try:
@@ -202,6 +317,9 @@ def try_switch_to_jca_if_git_repo(
202
317
  task: Optional[str],
203
318
  ) -> None:
204
319
  """在初始化环境前检测Git仓库,并可选择自动切换到代码开发模式(jca)。"""
320
+ # 非交互模式下跳过代码模式切换提示与相关输出
321
+ if is_non_interactive():
322
+ return
205
323
  if is_enable_git_repo_jca_switch():
206
324
  try:
207
325
  res = subprocess.run(
@@ -212,9 +330,7 @@ def try_switch_to_jca_if_git_repo(
212
330
  if res.returncode == 0:
213
331
  git_root = res.stdout.strip()
214
332
  if git_root and os.path.isdir(git_root):
215
- PrettyOutput.print(
216
- f"检测到当前位于 Git 仓库: {git_root}", OutputType.INFO
217
- )
333
+ print(f"ℹ️ 检测到当前位于 Git 仓库: {git_root}")
218
334
  if user_confirm(
219
335
  "检测到Git仓库,是否切换到代码开发模式(jca)?", default=False
220
336
  ):
@@ -230,10 +346,7 @@ def try_switch_to_jca_if_git_repo(
230
346
  args += ["--restore-session"]
231
347
  if task:
232
348
  args += ["-r", task]
233
- PrettyOutput.print(
234
- "正在切换到 'jca'(jarvis-code-agent)以进入代码开发模式...",
235
- OutputType.INFO,
236
- )
349
+ print("ℹ️ 正在切换到 'jca'(jarvis-code-agent)以进入代码开发模式...")
237
350
  os.execvp(args[0], args)
238
351
  except Exception:
239
352
  # 静默忽略检测异常,不影响主流程
@@ -249,10 +362,30 @@ def handle_builtin_config_selector(
249
362
  """在进入默认通用代理前,列出内置配置供选择(agent/multi_agent/roles)。"""
250
363
  if is_enable_builtin_config_selector():
251
364
  try:
252
- # 优先使用项目内置目录,若不存在则回退到指定的绝对路径
253
- builtin_root = Path(__file__).resolve().parents[3] / "builtin"
254
- if not builtin_root.exists():
255
- builtin_root = Path("/home/skyfire/code/Jarvis/builtin")
365
+ # 查找可用的 builtin 目录(支持多候选)
366
+ builtin_dirs: List[Path] = []
367
+ try:
368
+ ancestors = list(Path(__file__).resolve().parents)
369
+ for anc in ancestors[:8]:
370
+ p = anc / "builtin"
371
+ if p.exists():
372
+ builtin_dirs.append(p)
373
+ except Exception:
374
+ pass
375
+ # 去重,保留顺序
376
+ _seen = set()
377
+ _unique: List[Path] = []
378
+ for d in builtin_dirs:
379
+ try:
380
+ key = str(d.resolve())
381
+ except Exception:
382
+ key = str(d)
383
+ if key not in _seen:
384
+ _seen.add(key)
385
+ _unique.append(d)
386
+ builtin_dirs = _unique
387
+ # 向后兼容:保留第一个候选作为 builtin_root
388
+ builtin_root = builtin_dirs[0] if builtin_dirs else None # type: ignore[assignment]
256
389
 
257
390
  categories = [
258
391
  ("agent", "jarvis-agent", "*.yaml"),
@@ -293,8 +426,14 @@ def handle_builtin_config_selector(
293
426
  # 忽略配置读取异常
294
427
  pass
295
428
 
296
- # 追加内置目录
297
- search_dirs.append(builtin_root / cat)
429
+ # 追加内置目录(支持多个候选)
430
+ try:
431
+ candidates = builtin_dirs if isinstance(builtin_dirs, list) and builtin_dirs else ([builtin_root] if builtin_root else [])
432
+ except Exception:
433
+ candidates = ([builtin_root] if builtin_root else [])
434
+ for _bd in candidates:
435
+ if _bd:
436
+ search_dirs.append(Path(_bd) / cat)
298
437
 
299
438
  # 去重并保留顺序
300
439
  unique_dirs = []
@@ -308,11 +447,11 @@ def handle_builtin_config_selector(
308
447
  seen.add(key)
309
448
  unique_dirs.append(Path(d))
310
449
 
311
- # 每日自动更新配置目录(如目录为Git仓库则执行git pull,每日仅一次)
450
+ # 可选调试输出:查看每类的搜索目录
312
451
  try:
313
- jutils.daily_check_git_updates([str(p) for p in unique_dirs], cat)
452
+ if os.environ.get("JARVIS_DEBUG_BUILTIN_SELECTOR") == "1":
453
+ print(f"ℹ️ DEBUG: category={cat} search_dirs=" + ", ".join(str(p) for p in unique_dirs))
314
454
  except Exception:
315
- # 忽略更新过程中的所有异常,避免影响主流程
316
455
  pass
317
456
 
318
457
  for dir_path in unique_dirs:
@@ -374,7 +513,21 @@ def handle_builtin_config_selector(
374
513
  )
375
514
 
376
515
  if options:
377
- PrettyOutput.section("可用的内置配置", OutputType.SUCCESS)
516
+ # Add a default option to skip selection
517
+ options.insert(
518
+ 0,
519
+ {
520
+ "category": "skip",
521
+ "cmd": "",
522
+ "file": "",
523
+ "name": "跳过选择 (使用默认通用代理)",
524
+ "desc": "直接按回车或ESC也可跳过",
525
+ "details": "",
526
+ "roles_count": 0,
527
+ },
528
+ )
529
+
530
+ print("✅ 可用的内置配置")
378
531
  # 使用 rich Table 呈现
379
532
  table = Table(show_header=True, header_style="bold magenta")
380
533
  table.add_column("No.", style="cyan", no_wrap=True)
@@ -442,35 +595,45 @@ def handle_builtin_config_selector(
442
595
  if choice_index != -1:
443
596
  try:
444
597
  sel = options[choice_index]
445
- args: List[str] = []
446
-
447
- if sel["category"] == "agent":
448
- # jarvis-agent 支持 -f/--config(全局配置)与 -c/--agent-definition
449
- args = [str(sel["cmd"]), "-c", str(sel["file"])]
450
- if model_group:
451
- args += ["-g", str(model_group)]
452
- if config_file:
453
- args += ["-f", str(config_file)]
454
- if task:
455
- args += ["--task", str(task)]
456
-
457
- elif sel["category"] == "multi_agent":
458
- # jarvis-multi-agent 需要 -c/--config,用户输入通过 -i/--input 传递
459
- args = [str(sel["cmd"]), "-c", str(sel["file"])]
460
- if task:
461
- args += ["-i", str(task)]
462
-
463
- elif sel["category"] == "roles":
464
- # jarvis-platform-manager role 子命令,支持 -c/-t/-g
465
- args = [str(sel["cmd"]), "role", "-c", str(sel["file"])]
466
- if model_group:
467
- args += ["-g", str(model_group)]
468
-
469
- if args:
470
- PrettyOutput.print(
471
- f"正在启动: {' '.join(args)}", OutputType.INFO
472
- )
473
- os.execvp(args[0], args)
598
+ # If the "skip" option is chosen, do nothing and proceed to default agent
599
+ if sel["category"] == "skip":
600
+ pass
601
+ else:
602
+ args: List[str] = []
603
+
604
+ if sel["category"] == "agent":
605
+ # jarvis-agent 支持 -f/--config(全局配置)与 -c/--agent-definition
606
+ args = [str(sel["cmd"]), "-c", str(sel["file"])]
607
+ if model_group:
608
+ args += ["-g", str(model_group)]
609
+ if config_file:
610
+ args += ["-f", str(config_file)]
611
+ if task:
612
+ args += ["--task", str(task)]
613
+
614
+ elif sel["category"] == "multi_agent":
615
+ # jarvis-multi-agent 需要 -c/--config,用户输入通过 -i/--input 传递
616
+ # 同时传递 -g/--llm-group 以继承 jvs 的模型组选择
617
+ args = [str(sel["cmd"]), "-c", str(sel["file"])]
618
+ if model_group:
619
+ args += ["-g", str(model_group)]
620
+ if task:
621
+ args += ["-i", str(task)]
622
+
623
+ elif sel["category"] == "roles":
624
+ # jarvis-platform-manager role 子命令,支持 -c/-t/-g
625
+ args = [
626
+ str(sel["cmd"]),
627
+ "role",
628
+ "-c",
629
+ str(sel["file"]),
630
+ ]
631
+ if model_group:
632
+ args += ["-g", str(model_group)]
633
+
634
+ if args:
635
+ print(f"ℹ️ 正在启动: {' '.join(args)}")
636
+ os.execvp(args[0], args)
474
637
  except Exception:
475
638
  # 任何异常都不影响默认流程
476
639
  pass
@@ -521,6 +684,28 @@ def run_cli(
521
684
  "--disable-methodology-analysis",
522
685
  help="禁用方法论和任务分析(覆盖配置文件设置)",
523
686
  ),
687
+ backup_data: Optional[str] = typer.Option(
688
+ None,
689
+ "--backup-data",
690
+ help="备份 Jarvis 数据目录. 可选地传入备份目录. 默认为 '~/jarvis_backups'",
691
+ show_default=False,
692
+ flag_value="~/jarvis_backups",
693
+ ),
694
+ restore_data: Optional[str] = typer.Option(
695
+ None, "--restore-data", help="从指定的压缩包恢复 Jarvis 数据"
696
+ ),
697
+ non_interactive: bool = typer.Option(
698
+ False, "-n", "--non-interactive", help="启用非交互模式:用户无法与命令交互,脚本执行超时限制为5分钟"
699
+ ),
700
+ web: bool = typer.Option(False, "--web", help="以 Web 模式启动,通过浏览器 WebSocket 交互"),
701
+ web_host: str = typer.Option("127.0.0.1", "--web-host", help="Web 服务主机"),
702
+ web_port: int = typer.Option(8765, "--web-port", help="Web 服务端口"),
703
+ web_launch_cmd: Optional[str] = typer.Option(
704
+ None,
705
+ "--web-launch-cmd",
706
+ help="交互式终端启动命令(字符串格式,用空格分隔,如: --web-launch-cmd 'jca --task \"xxx\"')",
707
+ ),
708
+ stop: bool = typer.Option(False, "--stop", help="停止后台 Web 服务(需与 --web 一起使用)"),
524
709
  ) -> None:
525
710
  """Jarvis AI assistant command-line interface."""
526
711
  if ctx.invoked_subcommand is not None:
@@ -529,6 +714,42 @@ def run_cli(
529
714
  # 使用 rich 输出命令与快捷方式总览
530
715
  print_commands_overview()
531
716
 
717
+ # CLI 标志:非交互模式(不依赖配置文件)
718
+ if non_interactive:
719
+ try:
720
+ os.environ["JARVIS_NON_INTERACTIVE"] = "true"
721
+ except Exception:
722
+ pass
723
+ # 注意:全局配置同步在 init_env 之后执行,避免被覆盖
724
+
725
+ # 同步其他 CLI 选项到全局配置,确保后续模块读取一致
726
+ try:
727
+ if model_group:
728
+ set_config("JARVIS_LLM_GROUP", str(model_group))
729
+ if tool_group:
730
+ set_config("JARVIS_TOOL_GROUP", str(tool_group))
731
+ if disable_methodology_analysis:
732
+ set_config("JARVIS_USE_METHODOLOGY", False)
733
+ set_config("JARVIS_USE_ANALYSIS", False)
734
+ if restore_session:
735
+ set_config("JARVIS_RESTORE_SESSION", True)
736
+ except Exception:
737
+ # 静默忽略同步异常,不影响主流程
738
+ pass
739
+
740
+ # 非交互模式要求从命令行传入任务
741
+ if non_interactive and not (task and str(task).strip()):
742
+ print("❌ 非交互模式已启用:必须使用 --task 传入任务内容,因多行输入不可用。")
743
+ raise typer.Exit(code=2)
744
+
745
+ # 处理数据备份
746
+ if handle_backup_option(backup_data):
747
+ return
748
+
749
+ # 处理数据恢复
750
+ if handle_restore_option(restore_data, config_file):
751
+ return
752
+
532
753
  # 处理配置文件编辑
533
754
  if handle_edit_option(edit, config_file):
534
755
  return
@@ -547,35 +768,387 @@ def run_cli(
547
768
 
548
769
  # 预加载配置(仅用于读取功能开关),不会显示欢迎信息或影响后续 init_env
549
770
  preload_config_for_flags(config_file)
771
+ # Web 模式后台管理:支持 --web 后台启动与 --web --stop 停止
772
+ if web:
773
+ # PID 文件路径(按端口区分,便于多实例)
774
+ pidfile = Path(os.path.expanduser("~/.jarvis")) / f"jarvis_web_{web_port}.pid"
775
+ # 停止后台服务
776
+ if stop:
777
+ try:
778
+ pf = pidfile
779
+ if not pf.exists():
780
+ # 兼容旧版本:回退检查数据目录中的旧 PID 文件位置
781
+ try:
782
+ pf_alt = Path(os.path.expanduser(os.path.expandvars(get_data_dir()))) / f"jarvis_web_{web_port}.pid"
783
+ except Exception:
784
+ pf_alt = None # type: ignore[assignment]
785
+ if pf_alt and pf_alt.exists(): # type: ignore[truthy-bool]
786
+ pf = pf_alt
787
+ if not pf.exists():
788
+ # 进一步回退:尝试按端口查找并停止(无 PID 文件)
789
+ killed_any = False
790
+ try:
791
+ res = subprocess.run(
792
+ ["lsof", "-iTCP:%d" % web_port, "-sTCP:LISTEN", "-t"],
793
+ capture_output=True,
794
+ text=True,
795
+ )
796
+ if res.returncode == 0 and res.stdout.strip():
797
+ for ln in res.stdout.strip().splitlines():
798
+ try:
799
+ candidate_pid = int(ln.strip())
800
+ try:
801
+ os.kill(candidate_pid, signal.SIGTERM)
802
+ print(f"✅ 已按端口停止后台 Web 服务 (PID {candidate_pid})。")
803
+ killed_any = True
804
+ except Exception as e:
805
+ print(f"⚠️ 按端口停止失败: {e}")
806
+ except Exception:
807
+ continue
808
+ except Exception:
809
+ pass
810
+ if not killed_any:
811
+ try:
812
+ res2 = subprocess.run(["ss", "-ltpn"], capture_output=True, text=True)
813
+ if res2.returncode == 0 and res2.stdout:
814
+ for ln in res2.stdout.splitlines():
815
+ if f":{web_port} " in ln or f":{web_port}\n" in ln:
816
+ try:
817
+ idx = ln.find("pid=")
818
+ if idx != -1:
819
+ end = ln.find(",", idx)
820
+ pid_str2 = ln[idx+4:end if end != -1 else None]
821
+ candidate_pid = int(pid_str2)
822
+ try:
823
+ os.kill(candidate_pid, signal.SIGTERM)
824
+ print(f"✅ 已按端口停止后台 Web 服务 (PID {candidate_pid})。")
825
+ killed_any = True
826
+ except Exception as e:
827
+ print(f"⚠️ 按端口停止失败: {e}")
828
+ break
829
+ except Exception:
830
+ continue
831
+ except Exception:
832
+ pass
833
+ # 若仍未找到,扫描家目录下所有 Web PID 文件,尽力停止所有实例
834
+ if not killed_any:
835
+ try:
836
+ pid_dir = Path(os.path.expanduser("~/.jarvis"))
837
+ if pid_dir.is_dir():
838
+ for f in pid_dir.glob("jarvis_web_*.pid"):
839
+ try:
840
+ ptxt = f.read_text(encoding="utf-8").strip()
841
+ p = int(ptxt)
842
+ try:
843
+ os.kill(p, signal.SIGTERM)
844
+ print(f"✅ 已停止后台 Web 服务 (PID {p})。")
845
+ killed_any = True
846
+ except Exception as e:
847
+ print(f"⚠️ 停止 PID {p} 失败: {e}")
848
+ except Exception:
849
+ pass
850
+ try:
851
+ f.unlink(missing_ok=True)
852
+ except Exception:
853
+ pass
854
+ except Exception:
855
+ pass
856
+ if not killed_any:
857
+ print("⚠️ 未找到后台 Web 服务的 PID 文件,可能未启动或已停止。")
858
+ return
859
+ # 优先使用 PID 文件中的 PID
860
+ try:
861
+ pid_str = pf.read_text(encoding="utf-8").strip()
862
+ pid = int(pid_str)
863
+ except Exception:
864
+ pid = 0
865
+ killed = False
866
+ if pid > 0:
867
+ try:
868
+ os.kill(pid, signal.SIGTERM)
869
+ print(f"✅ 已向后台 Web 服务发送停止信号 (PID {pid})。")
870
+ killed = True
871
+ except Exception as e:
872
+ print(f"⚠️ 发送停止信号失败或进程不存在: {e}")
873
+ if not killed:
874
+ # 无 PID 文件或停止失败时,尝试按端口查找进程
875
+ candidate_pid = 0
876
+ try:
877
+ res = subprocess.run(
878
+ ["lsof", "-iTCP:%d" % web_port, "-sTCP:LISTEN", "-t"],
879
+ capture_output=True,
880
+ text=True,
881
+ )
882
+ if res.returncode == 0 and res.stdout.strip():
883
+ for ln in res.stdout.strip().splitlines():
884
+ try:
885
+ candidate_pid = int(ln.strip())
886
+ break
887
+ except Exception:
888
+ continue
889
+ except Exception:
890
+ pass
891
+ if not candidate_pid:
892
+ try:
893
+ res2 = subprocess.run(["ss", "-ltpn"], capture_output=True, text=True)
894
+ if res2.returncode == 0 and res2.stdout:
895
+ for ln in res2.stdout.splitlines():
896
+ if f":{web_port} " in ln or f":{web_port}\n" in ln:
897
+ # 格式示例: LISTEN ... users:(("uvicorn",pid=12345,fd=7))
898
+ try:
899
+ idx = ln.find("pid=")
900
+ if idx != -1:
901
+ end = ln.find(",", idx)
902
+ pid_str2 = ln[idx+4:end if end != -1 else None]
903
+ candidate_pid = int(pid_str2)
904
+ break
905
+ except Exception:
906
+ continue
907
+ except Exception:
908
+ pass
909
+ if candidate_pid:
910
+ try:
911
+ os.kill(candidate_pid, signal.SIGTERM)
912
+ print(f"✅ 已按端口停止后台 Web 服务 (PID {candidate_pid})。")
913
+ killed = True
914
+ except Exception as e:
915
+ print(f"⚠️ 按端口停止失败: {e}")
916
+ # 清理可能存在的 PID 文件(两个位置)
917
+ try:
918
+ pidfile.unlink(missing_ok=True) # 家目录位置
919
+ except Exception:
920
+ pass
921
+ try:
922
+ alt_pf = Path(os.path.expanduser(os.path.expandvars(get_data_dir()))) / f"jarvis_web_{web_port}.pid"
923
+ alt_pf.unlink(missing_ok=True)
924
+ except Exception:
925
+ pass
926
+ except Exception as e:
927
+ print(f"❌ 停止后台 Web 服务失败: {e}")
928
+ finally:
929
+ return
930
+ # 后台启动:父进程拉起子进程并记录 PID
931
+ is_daemon = False
932
+ try:
933
+ is_daemon = os.environ.get("JARVIS_WEB_DAEMON") == "1"
934
+ except Exception:
935
+ is_daemon = False
936
+ if not is_daemon:
937
+ try:
938
+ # 构建子进程参数,传递关键配置
939
+ args = [
940
+ sys.executable,
941
+ "-m",
942
+ "jarvis.jarvis_agent.jarvis",
943
+ "--web",
944
+ "--web-host",
945
+ str(web_host),
946
+ "--web-port",
947
+ str(web_port),
948
+ ]
949
+ if model_group:
950
+ args += ["-g", str(model_group)]
951
+ if tool_group:
952
+ args += ["-G", str(tool_group)]
953
+ if config_file:
954
+ args += ["-f", str(config_file)]
955
+ if restore_session:
956
+ args += ["--restore-session"]
957
+ if disable_methodology_analysis:
958
+ args += ["-D"]
959
+ if non_interactive:
960
+ args += ["-n"]
961
+ if web_launch_cmd:
962
+ args += ["--web-launch-cmd", str(web_launch_cmd)]
963
+ env = os.environ.copy()
964
+ env["JARVIS_WEB_DAEMON"] = "1"
965
+ # 启动子进程(后台运行)
966
+ proc = subprocess.Popen(
967
+ args,
968
+ env=env,
969
+ stdout=subprocess.DEVNULL,
970
+ stderr=subprocess.DEVNULL,
971
+ stdin=subprocess.DEVNULL,
972
+ close_fds=True,
973
+ )
974
+ # 记录 PID 到文件
975
+ try:
976
+ pidfile.parent.mkdir(parents=True, exist_ok=True)
977
+ except Exception:
978
+ pass
979
+ try:
980
+ pidfile.write_text(str(proc.pid), encoding="utf-8")
981
+ except Exception:
982
+ pass
983
+ print(f"✅ Web 服务已在后台启动 (PID {proc.pid}),地址: http://{web_host}:{web_port}")
984
+ except Exception as e:
985
+ print(f"❌ 后台启动 Web 服务失败: {e}")
986
+ raise typer.Exit(code=1)
987
+ return
550
988
 
551
989
  # 在初始化环境前检测Git仓库,并可选择自动切换到代码开发模式(jca)
552
- try_switch_to_jca_if_git_repo(
553
- model_group, tool_group, config_file, restore_session, task
554
- )
990
+ # 如果指定了 -T/--task 参数,跳过切换提示
991
+ if not non_interactive and not task:
992
+ try_switch_to_jca_if_git_repo(
993
+ model_group, tool_group, config_file, restore_session, task
994
+ )
555
995
 
556
996
  # 在进入默认通用代理前,列出内置配置供选择(agent/multi_agent/roles)
557
- handle_builtin_config_selector(model_group, tool_group, config_file, task)
997
+ # 非交互模式下跳过内置角色/配置选择
998
+ # 如果指定了 -T/--task 参数,跳过配置选择
999
+ if not non_interactive and not task:
1000
+ handle_builtin_config_selector(model_group, tool_group, config_file, task)
558
1001
 
559
1002
  # 初始化环境
560
1003
  init_env(
561
1004
  "欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
562
1005
  )
563
1006
 
1007
+ # 在初始化环境后同步 CLI 选项到全局配置,避免被 init_env 覆盖
1008
+ try:
1009
+ if model_group:
1010
+ set_config("JARVIS_LLM_GROUP", str(model_group))
1011
+ if tool_group:
1012
+ set_config("JARVIS_TOOL_GROUP", str(tool_group))
1013
+ if disable_methodology_analysis:
1014
+ set_config("JARVIS_USE_METHODOLOGY", False)
1015
+ set_config("JARVIS_USE_ANALYSIS", False)
1016
+ if restore_session:
1017
+ set_config("JARVIS_RESTORE_SESSION", True)
1018
+ if non_interactive:
1019
+ # 保持运行期非交互标志
1020
+ set_config("JARVIS_NON_INTERACTIVE", True)
1021
+ except Exception:
1022
+ # 静默忽略同步异常,不影响主流程
1023
+ pass
1024
+
564
1025
  # 运行主流程
565
1026
  try:
1027
+ # 在 Web 模式下注入基于 WebSocket 的输入/确认回调
1028
+ extra_kwargs = {}
1029
+ if web:
1030
+ # 纯 xterm 交互模式:不注入 WebBridge 的输入/确认回调,避免阻塞等待浏览器响应
1031
+ #(交互由 /terminal PTY 会话中的 jvs 进程处理)
1032
+ pass
1033
+
566
1034
  agent_manager = AgentManager(
567
1035
  model_group=model_group,
568
1036
  tool_group=tool_group,
569
1037
  restore_session=restore_session,
570
1038
  use_methodology=False if disable_methodology_analysis else None,
571
1039
  use_analysis=False if disable_methodology_analysis else None,
1040
+ non_interactive=non_interactive,
1041
+ **extra_kwargs,
572
1042
  )
573
1043
  agent_manager.initialize()
1044
+
1045
+ if web:
1046
+ try:
1047
+
1048
+ from jarvis.jarvis_agent.web_server import start_web_server
1049
+ from jarvis.jarvis_agent.stdio_redirect import enable_web_stdio_redirect, enable_web_stdin_redirect
1050
+ # 在 Web 模式下固定TTY宽度为200,改善前端显示效果
1051
+ try:
1052
+ import os as _os
1053
+ _os.environ["COLUMNS"] = "200"
1054
+ # 尝试固定全局 Console 的宽度
1055
+ try:
1056
+ from jarvis.jarvis_utils.globals import console as _console
1057
+ try:
1058
+ _console._width = 200 # rich Console的固定宽度参数
1059
+ except Exception:
1060
+ pass
1061
+ except Exception:
1062
+ pass
1063
+ except Exception:
1064
+ pass
1065
+ # 使用 STDIO 重定向,取消 Sink 广播以避免重复输出
1066
+ # 启用标准输出/错误的WebSocket重定向(捕获工具直接打印的输出)
1067
+ enable_web_stdio_redirect()
1068
+ # 启用来自前端 xterm 的 STDIN 重定向,使交互式命令可从浏览器获取输入
1069
+ try:
1070
+ enable_web_stdin_redirect()
1071
+ except Exception:
1072
+ pass
1073
+ # 构建用于交互式终端(PTY)重启的启动命令
1074
+ launch_cmd = None
1075
+ # 优先使用命令行参数指定的启动命令
1076
+ if web_launch_cmd and web_launch_cmd.strip():
1077
+ # 解析字符串命令(支持引号)
1078
+ try:
1079
+ import shlex
1080
+ launch_cmd = shlex.split(web_launch_cmd.strip())
1081
+ # 调试输出(可选,可以通过环境变量控制)
1082
+ if os.environ.get("JARVIS_DEBUG_WEB_LAUNCH_CMD") == "1":
1083
+ print(f"🔍 解析后的启动命令: {launch_cmd}")
1084
+ except Exception:
1085
+ # 如果解析失败,使用简单的空格分割
1086
+ launch_cmd = web_launch_cmd.strip().split()
1087
+ if os.environ.get("JARVIS_DEBUG_WEB_LAUNCH_CMD") == "1":
1088
+ print(f"🔍 使用简单分割的启动命令: {launch_cmd}")
1089
+ else:
1090
+ # 如果没有指定,则自动构建(移除 web 相关参数)
1091
+ try:
1092
+ import sys as _sys
1093
+ import os as _os
1094
+ _argv = list(_sys.argv)
1095
+ # 去掉程序名(argv[0]),并过滤 --web 相关参数
1096
+ filtered = []
1097
+ i = 1
1098
+ while i < len(_argv):
1099
+ a = _argv[i]
1100
+ if a == "--web" or a.startswith("--web="):
1101
+ i += 1
1102
+ continue
1103
+ if a == "--web-host":
1104
+ i += 2
1105
+ continue
1106
+ if a.startswith("--web-host="):
1107
+ i += 1
1108
+ continue
1109
+ if a == "--web-port":
1110
+ i += 2
1111
+ continue
1112
+ if a.startswith("--web-port="):
1113
+ i += 1
1114
+ continue
1115
+ if a == "--web-launch-cmd":
1116
+ # 跳过 --web-launch-cmd 及其值
1117
+ i += 2
1118
+ continue
1119
+ if a.startswith("--web-launch-cmd="):
1120
+ i += 1
1121
+ continue
1122
+ filtered.append(a)
1123
+ i += 1
1124
+ # 使用 jvs 命令作为可执行文件,保留其余业务参数
1125
+ launch_cmd = ["jvs"] + filtered
1126
+ except Exception:
1127
+ pass
1128
+
1129
+ # 同时写入环境变量作为备选(向后兼容)
1130
+ if launch_cmd:
1131
+ try:
1132
+ import os as _os
1133
+ import json as _json
1134
+ _os.environ["JARVIS_WEB_LAUNCH_JSON"] = _json.dumps(launch_cmd, ensure_ascii=False)
1135
+ except Exception:
1136
+ pass
1137
+
1138
+ print("ℹ️ 以 Web 模式启动,请在浏览器中打开提供的地址进行交互。")
1139
+ # 启动 Web 服务(阻塞调用),传入启动命令
1140
+ start_web_server(agent_manager, host=web_host, port=web_port, launch_command=launch_cmd)
1141
+ return
1142
+ except Exception as e:
1143
+ print(f"❌ Web 模式启动失败: {e}")
1144
+ raise typer.Exit(code=1)
1145
+
1146
+ # 默认 CLI 模式:运行任务(可能来自 --task 或交互输入)
574
1147
  agent_manager.run_task(task)
575
1148
  except typer.Exit:
576
1149
  raise
577
1150
  except Exception as err: # pylint: disable=broad-except
578
- PrettyOutput.print(f"初始化错误: {str(err)}", OutputType.ERROR)
1151
+ print(f"初始化错误: {str(err)}")
579
1152
  raise typer.Exit(code=1)
580
1153
 
581
1154