jarvis-ai-assistant 0.2.6__py3-none-any.whl → 0.2.8__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI Assistant"""
3
3
 
4
- __version__ = "0.2.6"
4
+ __version__ = "0.2.8"
@@ -614,8 +614,19 @@ class Agent:
614
614
  memory_tags = get_all_memory_tags()
615
615
  memory_tags_prompt = ""
616
616
 
617
+ # 检查是否有save_memory工具
618
+ tool_registry = self.get_tool_registry()
619
+ has_save_memory = False
620
+ if tool_registry:
621
+ tool_names = [tool.name for tool in tool_registry.tools.values()]
622
+ has_save_memory = "save_memory" in tool_names
623
+
624
+ # 如果有save_memory工具,添加记录关键信息的提示
625
+ if has_save_memory:
626
+ memory_tags_prompt = "\n\n💡 提示:在分析任务之前,建议使用 save_memory 工具将关键信息记录下来,便于后续检索和复用。"
627
+
617
628
  if any(tags for tags in memory_tags.values()):
618
- memory_tags_prompt = "\n\n系统中存在以下记忆标签,你可以使用 retrieve_memory 工具检索相关记忆:"
629
+ memory_tags_prompt += "\n\n系统中存在以下记忆标签,你可以使用 retrieve_memory 工具检索相关记忆:"
619
630
  for memory_type, tags in memory_tags.items():
620
631
  if tags:
621
632
  type_name = {
@@ -4,7 +4,7 @@ import shutil
4
4
  import subprocess
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Dict, Optional
7
+ from typing import Dict, Optional, List
8
8
 
9
9
  import typer
10
10
  import yaml # type: ignore
@@ -91,13 +91,19 @@ def _select_task(tasks: Dict[str, str]) -> str:
91
91
  selected_task = tasks[task_names[choice - 1]]
92
92
  PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
93
93
  # 询问是否需要补充信息
94
- need_additional = user_confirm("需要为此任务添加补充信息吗?", default=False)
94
+ need_additional = user_confirm(
95
+ "需要为此任务添加补充信息吗?", default=False
96
+ )
95
97
  if need_additional:
96
98
  additional_input = get_multiline_input("请输入补充信息:")
97
99
  if additional_input:
98
- selected_task = f"{selected_task}\n\n补充信息:\n{additional_input}"
100
+ selected_task = (
101
+ f"{selected_task}\n\n补充信息:\n{additional_input}"
102
+ )
99
103
  return selected_task
100
- PrettyOutput.print("无效的选择。请选择列表中的一个号码。", OutputType.WARNING)
104
+ PrettyOutput.print(
105
+ "无效的选择。请选择列表中的一个号码。", OutputType.WARNING
106
+ )
101
107
 
102
108
  except (KeyboardInterrupt, EOFError):
103
109
  return ""
@@ -183,6 +189,436 @@ def _get_and_run_task(agent: Agent, task_content: Optional[str] = None) -> None:
183
189
  raise typer.Exit(code=0)
184
190
 
185
191
 
192
+ def _parse_selection(selection_str: str, max_value: int) -> List[int]:
193
+ """解析用户输入的选择字符串,支持逗号分隔和范围选择
194
+
195
+ 例如: "1,2,3,4-9,20" -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
196
+ """
197
+ selected: set[int] = set()
198
+ parts = selection_str.split(",")
199
+
200
+ for part in parts:
201
+ part = part.strip()
202
+ if "-" in part:
203
+ # 处理范围选择
204
+ try:
205
+ start_str, end_str = part.split("-")
206
+ start_num = int(start_str.strip())
207
+ end_num = int(end_str.strip())
208
+ if 1 <= start_num <= max_value and 1 <= end_num <= max_value:
209
+ selected.update(range(start_num, end_num + 1))
210
+ except ValueError:
211
+ continue
212
+ else:
213
+ # 处理单个数字
214
+ try:
215
+ num = int(part)
216
+ if 1 <= num <= max_value:
217
+ selected.add(num)
218
+ except ValueError:
219
+ continue
220
+
221
+ return sorted(list(selected))
222
+
223
+
224
+ def _handle_share_tool(config_file: Optional[str] = None) -> None:
225
+ """处理工具分享功能"""
226
+ from jarvis.jarvis_utils.config import (
227
+ get_central_tool_repo,
228
+ get_data_dir,
229
+ )
230
+ import glob
231
+ import shutil
232
+
233
+ # 获取中心工具仓库配置
234
+ central_repo = get_central_tool_repo()
235
+ if not central_repo:
236
+ PrettyOutput.print(
237
+ "错误:未配置中心工具仓库(JARVIS_CENTRAL_TOOL_REPO)",
238
+ OutputType.ERROR,
239
+ )
240
+ PrettyOutput.print("请在配置文件中设置中心工具仓库的Git地址", OutputType.INFO)
241
+ raise typer.Exit(code=1)
242
+
243
+ # 克隆或更新中心工具仓库
244
+ central_repo_path = os.path.join(get_data_dir(), "central_tool_repo")
245
+ if not os.path.exists(central_repo_path):
246
+ PrettyOutput.print(f"正在克隆中心工具仓库...", OutputType.INFO)
247
+ subprocess.run(["git", "clone", central_repo, central_repo_path], check=True)
248
+ else:
249
+ PrettyOutput.print(f"正在更新中心工具仓库...", OutputType.INFO)
250
+ # 检查是否是空仓库
251
+ try:
252
+ # 先尝试获取远程分支信息
253
+ result = subprocess.run(
254
+ ["git", "ls-remote", "--heads", "origin"],
255
+ cwd=central_repo_path,
256
+ capture_output=True,
257
+ text=True,
258
+ check=True,
259
+ )
260
+ # 如果有远程分支,执行pull
261
+ if result.stdout.strip():
262
+ subprocess.run(["git", "pull"], cwd=central_repo_path, check=True)
263
+ else:
264
+ PrettyOutput.print(
265
+ "中心工具仓库是空的,将初始化为新仓库", OutputType.INFO
266
+ )
267
+ except subprocess.CalledProcessError:
268
+ # 如果命令失败,可能是网络问题或其他错误
269
+ PrettyOutput.print("无法连接到远程仓库,将跳过更新", OutputType.WARNING)
270
+
271
+ # 获取中心仓库中已有的工具文件名
272
+ existing_tools = set()
273
+ for filepath in glob.glob(os.path.join(central_repo_path, "*.py")):
274
+ existing_tools.add(os.path.basename(filepath))
275
+
276
+ # 只从数据目录的tools目录获取工具
277
+ local_tools_dir = os.path.join(get_data_dir(), "tools")
278
+ if not os.path.exists(local_tools_dir):
279
+ PrettyOutput.print(
280
+ f"本地工具目录不存在: {local_tools_dir}",
281
+ OutputType.WARNING,
282
+ )
283
+ raise typer.Exit(code=0)
284
+
285
+ # 收集本地工具文件(排除已存在的)
286
+ tool_files = []
287
+ for filepath in glob.glob(os.path.join(local_tools_dir, "*.py")):
288
+ filename = os.path.basename(filepath)
289
+ # 跳过__init__.py和已存在的文件
290
+ if filename == "__init__.py" or filename in existing_tools:
291
+ continue
292
+
293
+ # 尝试获取工具名称(通过简单解析)
294
+ tool_name = filename[:-3] # 移除.py后缀
295
+ tool_files.append(
296
+ {
297
+ "path": filepath,
298
+ "filename": filename,
299
+ "tool_name": tool_name,
300
+ }
301
+ )
302
+
303
+ if not tool_files:
304
+ PrettyOutput.print(
305
+ "没有找到新的工具文件(所有工具可能已存在于中心仓库)",
306
+ OutputType.WARNING,
307
+ )
308
+ raise typer.Exit(code=0)
309
+
310
+ # 显示可选的工具
311
+ tool_list = ["\n可分享的工具(已排除中心仓库中已有的):"]
312
+ for i, tool in enumerate(tool_files, 1):
313
+ tool_list.append(f"[{i}] {tool['tool_name']} ({tool['filename']})")
314
+
315
+ # 一次性打印所有工具
316
+ PrettyOutput.print("\n".join(tool_list), OutputType.INFO)
317
+
318
+ # 让用户选择要分享的工具
319
+ while True:
320
+ try:
321
+ choice_str = prompt(
322
+ "\n请选择要分享的工具编号(支持格式: 1,2,3,4-9,20 或 all):"
323
+ ).strip()
324
+ if choice_str == "0":
325
+ raise typer.Exit(code=0)
326
+
327
+ selected_tools = []
328
+ if choice_str.lower() == "all":
329
+ selected_tools = tool_files
330
+ else:
331
+ selected_indices = _parse_selection(choice_str, len(tool_files))
332
+ if not selected_indices:
333
+ PrettyOutput.print("无效的选择", OutputType.WARNING)
334
+ continue
335
+ selected_tools = [tool_files[i - 1] for i in selected_indices]
336
+
337
+ # 确认操作
338
+ share_list = [
339
+ "\n将要分享以下工具到中心仓库(注意:文件将被移动而非复制):"
340
+ ]
341
+ for tool in selected_tools:
342
+ share_list.append(f"- {tool['tool_name']} ({tool['filename']})")
343
+ PrettyOutput.print("\n".join(share_list), OutputType.WARNING)
344
+
345
+ if not user_confirm("确认移动这些工具到中心仓库吗?(原文件将被删除)"):
346
+ continue
347
+
348
+ # 移动选中的工具到中心仓库
349
+ moved_list = []
350
+ for tool in selected_tools:
351
+ src_file = tool["path"]
352
+ dst_file = os.path.join(central_repo_path, tool["filename"])
353
+ shutil.move(src_file, dst_file) # 使用move而不是copy
354
+ moved_list.append(f"已移动: {tool['tool_name']}")
355
+
356
+ # 一次性显示所有移动结果
357
+ if moved_list:
358
+ PrettyOutput.print("\n".join(moved_list), OutputType.SUCCESS)
359
+
360
+ # 提交并推送更改
361
+ PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
362
+ subprocess.run(["git", "add", "."], cwd=central_repo_path, check=True)
363
+
364
+ commit_msg = f"Add {len(selected_tools)} tool(s) from local collection"
365
+ subprocess.run(
366
+ ["git", "commit", "-m", commit_msg], cwd=central_repo_path, check=True
367
+ )
368
+
369
+ PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
370
+ # 检查是否需要设置上游分支(空仓库的情况)
371
+ try:
372
+ # 先尝试普通推送
373
+ subprocess.run(["git", "push"], cwd=central_repo_path, check=True)
374
+ except subprocess.CalledProcessError:
375
+ # 如果失败,可能是空仓库,尝试设置上游分支
376
+ try:
377
+ subprocess.run(
378
+ ["git", "push", "-u", "origin", "main"],
379
+ cwd=central_repo_path,
380
+ check=True,
381
+ )
382
+ except subprocess.CalledProcessError:
383
+ # 如果main分支不存在,尝试master分支
384
+ subprocess.run(
385
+ ["git", "push", "-u", "origin", "master"],
386
+ cwd=central_repo_path,
387
+ check=True,
388
+ )
389
+
390
+ PrettyOutput.print("\n工具已成功分享到中心仓库!", OutputType.SUCCESS)
391
+ PrettyOutput.print(
392
+ f"原文件已从 {local_tools_dir} 移动到中心仓库", OutputType.INFO
393
+ )
394
+ break
395
+
396
+ except ValueError:
397
+ PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
398
+ except subprocess.CalledProcessError as e:
399
+ PrettyOutput.print(f"Git操作失败: {str(e)}", OutputType.ERROR)
400
+ raise typer.Exit(code=1)
401
+ except Exception as e:
402
+ PrettyOutput.print(f"分享工具时出错: {str(e)}", OutputType.ERROR)
403
+ raise typer.Exit(code=1)
404
+
405
+
406
+ def _handle_share_methodology(config_file: Optional[str] = None) -> None:
407
+ """处理方法论分享功能"""
408
+ from jarvis.jarvis_utils.config import (
409
+ get_central_methodology_repo,
410
+ get_methodology_dirs,
411
+ get_data_dir,
412
+ )
413
+ import glob
414
+ import json
415
+ import shutil
416
+
417
+ # 获取中心方法论仓库配置
418
+ central_repo = get_central_methodology_repo()
419
+ if not central_repo:
420
+ PrettyOutput.print(
421
+ "错误:未配置中心方法论仓库(JARVIS_CENTRAL_METHODOLOGY_REPO)",
422
+ OutputType.ERROR,
423
+ )
424
+ PrettyOutput.print("请在配置文件中设置中心方法论仓库的Git地址", OutputType.INFO)
425
+ raise typer.Exit(code=1)
426
+
427
+ # 克隆或更新中心方法论仓库
428
+ central_repo_path = os.path.join(get_data_dir(), "central_methodology_repo")
429
+ if not os.path.exists(central_repo_path):
430
+ PrettyOutput.print(f"正在克隆中心方法论仓库...", OutputType.INFO)
431
+ subprocess.run(["git", "clone", central_repo, central_repo_path], check=True)
432
+ else:
433
+ PrettyOutput.print(f"正在更新中心方法论仓库...", OutputType.INFO)
434
+ # 检查是否是空仓库
435
+ try:
436
+ # 先尝试获取远程分支信息
437
+ result = subprocess.run(
438
+ ["git", "ls-remote", "--heads", "origin"],
439
+ cwd=central_repo_path,
440
+ capture_output=True,
441
+ text=True,
442
+ check=True,
443
+ )
444
+ # 如果有远程分支,执行pull
445
+ if result.stdout.strip():
446
+ subprocess.run(["git", "pull"], cwd=central_repo_path, check=True)
447
+ else:
448
+ PrettyOutput.print(
449
+ "中心方法论仓库是空的,将初始化为新仓库", OutputType.INFO
450
+ )
451
+ except subprocess.CalledProcessError:
452
+ # 如果命令失败,可能是网络问题或其他错误
453
+ PrettyOutput.print("无法连接到远程仓库,将跳过更新", OutputType.WARNING)
454
+
455
+ # 获取中心仓库中已有的方法论
456
+ existing_methodologies = {} # 改为字典,存储 problem_type -> content 的映射
457
+ for filepath in glob.glob(os.path.join(central_repo_path, "*.json")):
458
+ try:
459
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
460
+ methodology = json.load(f)
461
+ problem_type = methodology.get("problem_type", "")
462
+ content = methodology.get("content", "")
463
+ if problem_type and content:
464
+ existing_methodologies[problem_type] = content
465
+ except Exception:
466
+ pass
467
+
468
+ # 获取所有方法论目录
469
+ from jarvis.jarvis_utils.methodology import _get_methodology_directory
470
+
471
+ methodology_dirs = [_get_methodology_directory()] + get_methodology_dirs()
472
+
473
+ # 收集所有方法论文件(排除中心仓库目录和已存在的方法论)
474
+ all_methodologies = {}
475
+ methodology_files = []
476
+ seen_problem_types = set() # 用于去重
477
+
478
+ for directory in set(methodology_dirs):
479
+ # 跳过中心仓库目录
480
+ if os.path.abspath(directory) == os.path.abspath(central_repo_path):
481
+ continue
482
+
483
+ if not os.path.isdir(directory):
484
+ continue
485
+
486
+ for filepath in glob.glob(os.path.join(directory, "*.json")):
487
+ try:
488
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
489
+ methodology = json.load(f)
490
+ problem_type = methodology.get("problem_type", "")
491
+ content = methodology.get("content", "")
492
+ # 基于内容判断是否已存在于中心仓库
493
+ is_duplicate = False
494
+ if problem_type in existing_methodologies:
495
+ # 如果problem_type相同,比较内容
496
+ if (
497
+ content.strip()
498
+ == existing_methodologies[problem_type].strip()
499
+ ):
500
+ is_duplicate = True
501
+
502
+ # 排除已存在于中心仓库的方法论(基于内容),以及本地重复的方法论
503
+ if (
504
+ problem_type
505
+ and content
506
+ and not is_duplicate
507
+ and problem_type not in seen_problem_types
508
+ ):
509
+ methodology_files.append(
510
+ {
511
+ "path": filepath,
512
+ "problem_type": problem_type,
513
+ "directory": directory,
514
+ }
515
+ )
516
+ all_methodologies[problem_type] = methodology
517
+ seen_problem_types.add(problem_type)
518
+ except Exception:
519
+ pass
520
+
521
+ if not methodology_files:
522
+ PrettyOutput.print(
523
+ "没有找到新的方法论文件(所有方法论可能已存在于中心仓库)",
524
+ OutputType.WARNING,
525
+ )
526
+ raise typer.Exit(code=0)
527
+
528
+ # 显示可选的方法论
529
+ methodology_list = ["\n可分享的方法论(已排除中心仓库中已有的):"]
530
+ for i, meth in enumerate(methodology_files, 1):
531
+ dir_name = os.path.basename(meth["directory"])
532
+ methodology_list.append(f"[{i}] {meth['problem_type']} (来自: {dir_name})")
533
+
534
+ # 一次性打印所有方法论
535
+ PrettyOutput.print("\n".join(methodology_list), OutputType.INFO)
536
+
537
+ # 让用户选择要分享的方法论
538
+ while True:
539
+ try:
540
+ choice_str = prompt(
541
+ "\n请选择要分享的方法论编号(支持格式: 1,2,3,4-9,20 或 all):"
542
+ ).strip()
543
+ if choice_str == "0":
544
+ raise typer.Exit(code=0)
545
+
546
+ selected_methodologies = []
547
+ if choice_str.lower() == "all":
548
+ selected_methodologies = methodology_files
549
+ else:
550
+ selected_indices = _parse_selection(choice_str, len(methodology_files))
551
+ if not selected_indices:
552
+ PrettyOutput.print("无效的选择", OutputType.WARNING)
553
+ continue
554
+ selected_methodologies = [
555
+ methodology_files[i - 1] for i in selected_indices
556
+ ]
557
+
558
+ # 确认操作
559
+ share_list = ["\n将要分享以下方法论到中心仓库:"]
560
+ for meth in selected_methodologies:
561
+ share_list.append(f"- {meth['problem_type']}")
562
+ PrettyOutput.print("\n".join(share_list), OutputType.INFO)
563
+
564
+ if not user_confirm("确认分享这些方法论吗?"):
565
+ continue
566
+
567
+ # 复制选中的方法论到中心仓库
568
+ copied_list = []
569
+ for meth in selected_methodologies:
570
+ src_file = meth["path"]
571
+ dst_file = os.path.join(central_repo_path, os.path.basename(src_file))
572
+ shutil.copy2(src_file, dst_file)
573
+ copied_list.append(f"已复制: {meth['problem_type']}")
574
+
575
+ # 一次性显示所有复制结果
576
+ if copied_list:
577
+ PrettyOutput.print("\n".join(copied_list), OutputType.SUCCESS)
578
+
579
+ # 提交并推送更改
580
+ PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
581
+ subprocess.run(["git", "add", "."], cwd=central_repo_path, check=True)
582
+
583
+ commit_msg = f"Add {len(selected_methodologies)} methodology(ies) from local collection"
584
+ subprocess.run(
585
+ ["git", "commit", "-m", commit_msg], cwd=central_repo_path, check=True
586
+ )
587
+
588
+ PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
589
+ # 检查是否需要设置上游分支(空仓库的情况)
590
+ try:
591
+ # 先尝试普通推送
592
+ subprocess.run(["git", "push"], cwd=central_repo_path, check=True)
593
+ except subprocess.CalledProcessError:
594
+ # 如果失败,可能是空仓库,尝试设置上游分支
595
+ try:
596
+ subprocess.run(
597
+ ["git", "push", "-u", "origin", "main"],
598
+ cwd=central_repo_path,
599
+ check=True,
600
+ )
601
+ except subprocess.CalledProcessError:
602
+ # 如果main分支不存在,尝试master分支
603
+ subprocess.run(
604
+ ["git", "push", "-u", "origin", "master"],
605
+ cwd=central_repo_path,
606
+ check=True,
607
+ )
608
+
609
+ PrettyOutput.print("\n方法论已成功分享到中心仓库!", OutputType.SUCCESS)
610
+ break
611
+
612
+ except ValueError:
613
+ PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
614
+ except subprocess.CalledProcessError as e:
615
+ PrettyOutput.print(f"Git操作失败: {str(e)}", OutputType.ERROR)
616
+ raise typer.Exit(code=1)
617
+ except Exception as e:
618
+ PrettyOutput.print(f"分享方法论时出错: {str(e)}", OutputType.ERROR)
619
+ raise typer.Exit(code=1)
620
+
621
+
186
622
  @app.callback(invoke_without_command=True)
187
623
  def run_cli(
188
624
  ctx: typer.Context,
@@ -191,17 +627,27 @@ def run_cli(
191
627
  "--llm_type",
192
628
  help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
193
629
  ),
194
- task: Optional[str] = typer.Option(None, "-t", "--task", help="从命令行直接输入任务内容"),
630
+ task: Optional[str] = typer.Option(
631
+ None, "-t", "--task", help="从命令行直接输入任务内容"
632
+ ),
195
633
  model_group: Optional[str] = typer.Option(
196
634
  None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
197
635
  ),
198
- config_file: Optional[str] = typer.Option(None, "-f", "--config", help="自定义配置文件路径"),
636
+ config_file: Optional[str] = typer.Option(
637
+ None, "-f", "--config", help="自定义配置文件路径"
638
+ ),
199
639
  restore_session: bool = typer.Option(
200
640
  False,
201
641
  "--restore-session",
202
642
  help="从 .jarvis/saved_session.json 恢复会话",
203
643
  ),
204
644
  edit: bool = typer.Option(False, "-e", "--edit", help="编辑配置文件"),
645
+ share_methodology: bool = typer.Option(
646
+ False, "--share-methodology", help="分享本地方法论到中心方法论仓库"
647
+ ),
648
+ share_tool: bool = typer.Option(
649
+ False, "--share-tool", help="分享本地工具到中心工具仓库"
650
+ ),
205
651
  ) -> None:
206
652
  """Jarvis AI assistant command-line interface."""
207
653
  if ctx.invoked_subcommand is not None:
@@ -209,7 +655,21 @@ def run_cli(
209
655
 
210
656
  _handle_edit_mode(edit, config_file)
211
657
 
212
- init_env("欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file)
658
+ # 处理方法论分享
659
+ if share_methodology:
660
+ init_env("", config_file=config_file) # 初始化配置但不显示欢迎信息
661
+ _handle_share_methodology(config_file)
662
+ raise typer.Exit(code=0)
663
+
664
+ # 处理工具分享
665
+ if share_tool:
666
+ init_env("", config_file=config_file) # 初始化配置但不显示欢迎信息
667
+ _handle_share_tool(config_file)
668
+ raise typer.Exit(code=0)
669
+
670
+ init_env(
671
+ "欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
672
+ )
213
673
 
214
674
  try:
215
675
  agent = _initialize_agent(llm_type, model_group, restore_session)
@@ -67,6 +67,7 @@ class CodeAgent:
67
67
  "rewrite_file",
68
68
  "save_memory",
69
69
  "retrieve_memory",
70
+ "clear_memory",
70
71
  ]
71
72
  )
72
73
  code_system_prompt = """
@@ -225,6 +225,16 @@
225
225
  },
226
226
  "default": []
227
227
  },
228
+ "JARVIS_CENTRAL_METHODOLOGY_REPO": {
229
+ "type": "string",
230
+ "description": "中心方法论Git仓库地址,该仓库会自动添加到方法论加载路径中",
231
+ "default": ""
232
+ },
233
+ "JARVIS_CENTRAL_TOOL_REPO": {
234
+ "type": "string",
235
+ "description": "中心工具库Git仓库地址,该仓库会自动克隆到数据目录并加载其中的工具",
236
+ "default": ""
237
+ },
228
238
  "JARVIS_PRINT_PROMPT": {
229
239
  "type": "boolean",
230
240
  "description": "是否打印提示",
@@ -245,7 +245,7 @@ class StatsStorage:
245
245
  metrics_from_meta = set(meta.get("metrics", {}).keys())
246
246
 
247
247
  # 扫描所有数据文件获取实际存在的指标
248
- metrics_from_data = set()
248
+ metrics_from_data: set[str] = set()
249
249
  for data_file in self.data_dir.glob("stats_*.json"):
250
250
  try:
251
251
  data = self._load_json(data_file)
@@ -285,7 +285,7 @@ class StatsStorage:
285
285
  return {}
286
286
 
287
287
  # 聚合数据
288
- aggregated = defaultdict(
288
+ aggregated: Dict[str, Dict[str, Any]] = defaultdict(
289
289
  lambda: {
290
290
  "count": 0,
291
291
  "sum": 0,