jarvis-ai-assistant 0.3.14__py3-none-any.whl → 0.3.16__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.3.14"
4
+ __version__ = "0.3.16"
@@ -10,6 +10,14 @@ from jarvis.jarvis_agent.config_editor import ConfigEditor
10
10
  from jarvis.jarvis_agent.methodology_share_manager import MethodologyShareManager
11
11
  from jarvis.jarvis_agent.tool_share_manager import ToolShareManager
12
12
  from jarvis.jarvis_utils.utils import init_env
13
+ from jarvis.jarvis_utils.input import user_confirm, get_single_line_input
14
+ import os
15
+ import sys
16
+ import subprocess
17
+ from pathlib import Path
18
+ import yaml # type: ignore
19
+ from rich.table import Table
20
+ from rich.console import Console
13
21
 
14
22
  app = typer.Typer(help="Jarvis AI 助手")
15
23
 
@@ -23,14 +31,18 @@ def run_cli(
23
31
  "--llm-type",
24
32
  help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
25
33
  ),
26
- task: Optional[str] = typer.Option(None, "-T", "--task", help="从命令行直接输入任务内容"),
34
+ task: Optional[str] = typer.Option(
35
+ None, "-T", "--task", help="从命令行直接输入任务内容"
36
+ ),
27
37
  model_group: Optional[str] = typer.Option(
28
38
  None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
29
39
  ),
30
40
  tool_group: Optional[str] = typer.Option(
31
41
  None, "-G", "--tool-group", help="使用的工具组,覆盖配置文件中的设置"
32
42
  ),
33
- config_file: Optional[str] = typer.Option(None, "-f", "--config", help="自定义配置文件路径"),
43
+ config_file: Optional[str] = typer.Option(
44
+ None, "-f", "--config", help="自定义配置文件路径"
45
+ ),
34
46
  restore_session: bool = typer.Option(
35
47
  False,
36
48
  "--restore-session",
@@ -40,7 +52,9 @@ def run_cli(
40
52
  share_methodology: bool = typer.Option(
41
53
  False, "--share-methodology", help="分享本地方法论到中心方法论仓库"
42
54
  ),
43
- share_tool: bool = typer.Option(False, "--share-tool", help="分享本地工具到中心工具仓库"),
55
+ share_tool: bool = typer.Option(
56
+ False, "--share-tool", help="分享本地工具到中心工具仓库"
57
+ ),
44
58
  ) -> None:
45
59
  """Jarvis AI assistant command-line interface."""
46
60
  if ctx.invoked_subcommand is not None:
@@ -65,8 +79,186 @@ def run_cli(
65
79
  tool_manager.run()
66
80
  return
67
81
 
82
+ # 在初始化环境前检测Git仓库,并可选择自动切换到代码开发模式(jca)
83
+ try:
84
+ res = subprocess.run(
85
+ ["git", "rev-parse", "--show-toplevel"],
86
+ capture_output=True,
87
+ text=True,
88
+ )
89
+ if res.returncode == 0:
90
+ git_root = res.stdout.strip()
91
+ if git_root and os.path.isdir(git_root):
92
+ PrettyOutput.print(
93
+ f"检测到当前位于 Git 仓库: {git_root}", OutputType.INFO
94
+ )
95
+ if user_confirm(
96
+ "检测到Git仓库,是否切换到代码开发模式(jca)?", default=False
97
+ ):
98
+ # 构建并切换到 jarvis-code-agent 命令,传递兼容参数
99
+ args = ["jarvis-code-agent"]
100
+ if llm_type:
101
+ args += ["-t", llm_type]
102
+ if model_group:
103
+ args += ["-g", model_group]
104
+ if tool_group:
105
+ args += ["-G", tool_group]
106
+ if config_file:
107
+ args += ["-f", config_file]
108
+ if restore_session:
109
+ args += ["--restore-session"]
110
+ if task:
111
+ args += ["-r", task]
112
+ PrettyOutput.print(
113
+ "正在切换到 'jca'(jarvis-code-agent)以进入代码开发模式...",
114
+ OutputType.INFO,
115
+ )
116
+ os.execvp(args[0], args)
117
+ except Exception:
118
+ # 静默忽略检测异常,不影响主流程
119
+ pass
120
+
121
+ # 在进入默认通用代理前,列出内置配置供选择(agent/multi_agent/roles)
122
+ try:
123
+ # 优先使用项目内置目录,若不存在则回退到指定的绝对路径
124
+ builtin_root = Path(__file__).resolve().parents[3] / "builtin"
125
+ if not builtin_root.exists():
126
+ builtin_root = Path("/home/skyfire/code/Jarvis/builtin")
127
+
128
+ categories = [
129
+ ("agent", "jarvis-agent", "*.yaml"),
130
+ ("multi_agent", "jarvis-multi-agent", "*.yaml"),
131
+ ("roles", "jarvis-platform-manager", "*.yaml"),
132
+ ]
133
+
134
+ options = []
135
+ for cat, cmd, pattern in categories:
136
+ dir_path = builtin_root / cat
137
+ if not dir_path.exists():
138
+ continue
139
+ for fpath in sorted(dir_path.glob(pattern)):
140
+ # 解析YAML以获取可读名称/描述(失败时静默降级为文件名)
141
+ name = fpath.stem
142
+ desc = ""
143
+ try:
144
+ with open(fpath, "r", encoding="utf-8", errors="ignore") as fh:
145
+ data = yaml.safe_load(fh) or {}
146
+ if isinstance(data, dict):
147
+ name = data.get("name") or data.get("title") or name
148
+ desc = data.get("description") or data.get("desc") or ""
149
+ if cat == "roles" and isinstance(data.get("roles"), list):
150
+ if not desc:
151
+ desc = f"{len(data['roles'])} 个角色"
152
+ except Exception:
153
+ # 忽略解析错误,使用默认显示
154
+ pass
155
+
156
+ # 为 roles 构建详细信息(每个角色的名称与描述)
157
+ details = ""
158
+ if cat == "roles":
159
+ roles = (data or {}).get("roles", [])
160
+ if isinstance(roles, list):
161
+ lines = []
162
+ for role in roles:
163
+ if isinstance(role, dict):
164
+ rname = str(role.get("name", "") or "")
165
+ rdesc = str(role.get("description", "") or "")
166
+ lines.append(f"{rname} - {rdesc}" if rdesc else rname)
167
+ details = "\n".join([ln for ln in lines if ln])
168
+ # 如果没有角色详情,退回到统计信息
169
+ if not details and isinstance((data or {}).get("roles"), list):
170
+ details = f"{len(data['roles'])} 个角色"
171
+
172
+ options.append(
173
+ {
174
+ "category": cat,
175
+ "cmd": cmd,
176
+ "file": str(fpath),
177
+ "name": str(name),
178
+ "desc": str(desc),
179
+ "details": str(details),
180
+ }
181
+ )
182
+
183
+ if options:
184
+ PrettyOutput.section("可用的内置配置", OutputType.SUCCESS)
185
+ # 使用 rich Table 呈现
186
+ table = Table(show_header=True, header_style="bold magenta")
187
+ table.add_column("No.", style="cyan", no_wrap=True)
188
+ table.add_column("类型", style="green", no_wrap=True)
189
+ table.add_column("名称", style="bold")
190
+ table.add_column("文件", style="dim")
191
+ table.add_column("描述/详情", style="white")
192
+
193
+ for idx, opt in enumerate(options, 1):
194
+ category = opt.get("category", "")
195
+ name = opt.get("name", "")
196
+ file_path = opt.get("file", "")
197
+ # multi_agent: 优先显示顶层描述
198
+ # roles: 显示每个角色名称与描述(多行)
199
+ # 其他:显示 desc
200
+ if category == "roles" and opt.get("details"):
201
+ detail = opt["details"]
202
+ else:
203
+ detail = opt.get("desc", "")
204
+
205
+ table.add_row(str(idx), category, name, file_path, detail)
206
+
207
+ Console().print(table)
208
+
209
+ choice = get_single_line_input(
210
+ "选择要启动的配置编号,直接回车使用默认通用代理(jvs): ", default=""
211
+ )
212
+
213
+ if choice.strip():
214
+ try:
215
+ index = int(choice.strip())
216
+ if 1 <= index <= len(options):
217
+ sel = options[index - 1]
218
+ args = []
219
+
220
+ if sel["category"] == "agent":
221
+ # jarvis-agent 支持 -f/--config(全局配置)与 -c/--agent-definition
222
+ args = [sel["cmd"], "-c", sel["file"]]
223
+ if llm_type:
224
+ args += ["--llm-type", llm_type]
225
+ if model_group:
226
+ args += ["-g", model_group]
227
+ if config_file:
228
+ args += ["-f", config_file]
229
+ if task:
230
+ args += ["--task", task]
231
+
232
+ elif sel["category"] == "multi_agent":
233
+ # jarvis-multi-agent 需要 -c/--config,用户输入通过 -i/--input 传递
234
+ args = [sel["cmd"], "-c", sel["file"]]
235
+ if task:
236
+ args += ["-i", task]
237
+
238
+ elif sel["category"] == "roles":
239
+ # jarvis-platform-manager role 子命令,支持 -c/-t/-g
240
+ args = [sel["cmd"], "role", "-c", sel["file"]]
241
+ if llm_type:
242
+ args += ["-t", llm_type]
243
+ if model_group:
244
+ args += ["-g", model_group]
245
+
246
+ if args:
247
+ PrettyOutput.print(
248
+ f"正在启动: {' '.join(args)}", OutputType.INFO
249
+ )
250
+ os.execvp(args[0], args)
251
+ except Exception:
252
+ # 任何异常都不影响默认流程
253
+ pass
254
+ except Exception:
255
+ # 静默忽略内置配置扫描错误,不影响主流程
256
+ pass
257
+
68
258
  # 初始化环境
69
- init_env("欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file)
259
+ init_env(
260
+ "欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
261
+ )
70
262
 
71
263
  # 运行主流程
72
264
  try:
@@ -3,7 +3,7 @@ import os
3
3
  from typing import Optional
4
4
 
5
5
  import typer
6
- import yaml
6
+ import yaml # type: ignore[import-untyped]
7
7
 
8
8
  from jarvis.jarvis_agent import Agent
9
9
  from jarvis.jarvis_utils.input import get_multiline_input
@@ -45,20 +45,22 @@ def cli(
45
45
  agent_definition: Optional[str] = typer.Option(
46
46
  None, "-c", "--agent-definition", help="代理定义文件路径"
47
47
  ),
48
- task: Optional[str] = typer.Option(
49
- None, "-t", "--task", help="初始任务内容"
50
- ),
48
+ task: Optional[str] = typer.Option(None, "-T", "--task", help="初始任务内容"),
51
49
  llm_type: str = typer.Option(
52
50
  "normal",
53
- "-t", "--llm-type",
51
+ "-t",
52
+ "--llm-type",
54
53
  help="使用的LLM类型,覆盖配置文件中的设置",
55
54
  ),
56
55
  model_group: Optional[str] = typer.Option(
57
56
  None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
58
- ),):
57
+ ),
58
+ ):
59
59
  """Main entry point for Jarvis agent"""
60
60
  # Initialize environment
61
- init_env("欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file)
61
+ init_env(
62
+ "欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
63
+ )
62
64
 
63
65
  # Load configuration
64
66
  config = load_config(agent_definition) if agent_definition else {}
@@ -77,21 +79,27 @@ def cli(
77
79
  if task:
78
80
  PrettyOutput.print(f"执行初始任务: {task}", OutputType.INFO)
79
81
  agent.run(task)
80
- raise typer.Exit(code=0)
82
+ return
81
83
 
82
84
  try:
83
85
  user_input = get_multiline_input("请输入你的任务(输入空行退出):")
84
86
  if not user_input:
85
- raise typer.Exit(code=0)
87
+ return
86
88
  agent.set_addon_prompt(
87
89
  "如果有必要,请先指定出行动计划,然后根据计划一步步执行,如果任务过于复杂,可以拆分子Agent进行执行,拆的子Agent需要掌握所有必要的任务信息,否则无法执行"
88
90
  )
89
91
  agent.run(user_input)
92
+ except KeyboardInterrupt:
93
+ # 用户主动取消输入,正常退出
94
+ return
95
+ except typer.Exit:
96
+ # 来自输入流程的正常退出
97
+ return
90
98
  except Exception as e:
91
99
  PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
92
100
 
93
101
  except typer.Exit:
94
- raise
102
+ return
95
103
  except Exception as e:
96
104
  PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
97
105
  raise typer.Exit(code=1)
@@ -52,8 +52,10 @@ class CodeAgent:
52
52
  model_group: Optional[str] = None,
53
53
  need_summary: bool = True,
54
54
  append_tools: Optional[str] = None,
55
+ tool_group: Optional[str] = None,
55
56
  ):
56
57
  self.root_dir = os.getcwd()
58
+ self.tool_group = tool_group
57
59
 
58
60
  # 检测 git username 和 email 是否已设置
59
61
  self._check_git_config()
@@ -233,18 +235,18 @@ class CodeAgent:
233
235
  else:
234
236
  print("ℹ️ .jarvis已在.gitignore中")
235
237
 
236
- def _handle_git_changes(self) -> None:
238
+ def _handle_git_changes(self, prefix: str, suffix: str) -> None:
237
239
  """处理git仓库中的未提交修改"""
238
240
  print("🔄 正在检查未提交的修改...")
239
241
  if has_uncommitted_changes():
240
242
  print("⏳ 发现未提交修改,正在处理...")
241
243
  git_commiter = GitCommitTool()
242
- git_commiter.execute({})
244
+ git_commiter.execute({"prefix": prefix, "suffix": suffix})
243
245
  print("✅ 未提交修改已处理完成")
244
246
  else:
245
247
  print("✅ 没有未提交的修改")
246
248
 
247
- def _init_env(self) -> None:
249
+ def _init_env(self, prefix: str, suffix: str) -> None:
248
250
  """初始化环境,组合以下功能:
249
251
  1. 查找git根目录
250
252
  2. 检查并更新.gitignore文件
@@ -254,7 +256,7 @@ class CodeAgent:
254
256
  print("🚀 正在初始化环境...")
255
257
  git_dir = self._find_git_root()
256
258
  self._update_gitignore(git_dir)
257
- self._handle_git_changes()
259
+ self._handle_git_changes(prefix, suffix)
258
260
  # 配置git对换行符变化不敏感
259
261
  self._configure_line_ending_settings()
260
262
  print("✅ 环境初始化完成")
@@ -473,7 +475,11 @@ class CodeAgent:
473
475
  return commits
474
476
 
475
477
  def _handle_commit_confirmation(
476
- self, commits: List[Tuple[str, str]], start_commit: Optional[str]
478
+ self,
479
+ commits: List[Tuple[str, str]],
480
+ start_commit: Optional[str],
481
+ prefix: str,
482
+ suffix: str,
477
483
  ) -> None:
478
484
  """处理提交确认和可能的重置"""
479
485
  if commits and user_confirm("是否接受以上提交记录?", True):
@@ -489,7 +495,7 @@ class CodeAgent:
489
495
  check=True,
490
496
  )
491
497
  git_commiter = GitCommitTool()
492
- git_commiter.execute({})
498
+ git_commiter.execute({"prefix": prefix, "suffix": suffix})
493
499
 
494
500
  # 在用户接受commit后,根据配置决定是否保存记忆
495
501
  if self.agent.force_save_memory:
@@ -498,7 +504,7 @@ class CodeAgent:
498
504
  os.system(f"git reset --hard {str(start_commit)}") # 确保转换为字符串
499
505
  PrettyOutput.print("已重置到初始提交", OutputType.INFO)
500
506
 
501
- def run(self, user_input: str) -> Optional[str]:
507
+ def run(self, user_input: str, prefix: str = "", suffix: str = "") -> Optional[str]:
502
508
  """使用给定的用户输入运行代码代理。
503
509
 
504
510
  参数:
@@ -508,7 +514,7 @@ class CodeAgent:
508
514
  str: 描述执行结果的输出,成功时返回None
509
515
  """
510
516
  try:
511
- self._init_env()
517
+ self._init_env(prefix, suffix)
512
518
  start_commit = get_latest_commit_hash()
513
519
 
514
520
  # 获取项目统计信息并附加到用户输入
@@ -557,7 +563,7 @@ class CodeAgent:
557
563
  self._handle_uncommitted_changes()
558
564
  end_commit = get_latest_commit_hash()
559
565
  commits = self._show_commit_history(start_commit, end_commit)
560
- self._handle_commit_confirmation(commits, start_commit)
566
+ self._handle_commit_confirmation(commits, start_commit, prefix, suffix)
561
567
  return None
562
568
 
563
569
  except RuntimeError as e:
@@ -660,6 +666,12 @@ def cli(
660
666
  model_group: Optional[str] = typer.Option(
661
667
  None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
662
668
  ),
669
+ tool_group: Optional[str] = typer.Option(
670
+ None, "-G", "--tool-group", help="使用的工具组,覆盖配置文件中的设置"
671
+ ),
672
+ config_file: Optional[str] = typer.Option(
673
+ None, "-f", "--config", help="配置文件路径"
674
+ ),
663
675
  requirement: Optional[str] = typer.Option(
664
676
  None, "-r", "--requirement", help="要处理的需求描述"
665
677
  ),
@@ -671,9 +683,22 @@ def cli(
671
683
  "--restore-session",
672
684
  help="从 .jarvis/saved_session.json 恢复会话状态",
673
685
  ),
686
+ prefix: str = typer.Option(
687
+ "",
688
+ "--prefix",
689
+ help="提交信息前缀(用空格分隔)",
690
+ ),
691
+ suffix: str = typer.Option(
692
+ "",
693
+ "--suffix",
694
+ help="提交信息后缀(用换行分隔)",
695
+ ),
674
696
  ) -> None:
675
697
  """Jarvis主入口点。"""
676
- init_env("欢迎使用 Jarvis-CodeAgent,您的代码工程助手已准备就绪!")
698
+ init_env(
699
+ "欢迎使用 Jarvis-CodeAgent,您的代码工程助手已准备就绪!",
700
+ config_file=config_file,
701
+ )
677
702
 
678
703
  try:
679
704
  subprocess.run(
@@ -717,6 +742,7 @@ def cli(
717
742
  model_group=model_group,
718
743
  need_summary=False,
719
744
  append_tools=append_tools,
745
+ tool_group=tool_group,
720
746
  )
721
747
 
722
748
  # 尝试恢复会话
@@ -731,13 +757,13 @@ def cli(
731
757
  )
732
758
 
733
759
  if requirement:
734
- agent.run(requirement)
760
+ agent.run(requirement, prefix=prefix, suffix=suffix)
735
761
  else:
736
762
  while True:
737
763
  user_input = get_multiline_input("请输入你的需求(输入空行退出):")
738
764
  if not user_input:
739
765
  raise typer.Exit(code=0)
740
- agent.run(user_input)
766
+ agent.run(user_input, prefix=prefix, suffix=suffix)
741
767
 
742
768
  except typer.Exit:
743
769
  raise
@@ -4,7 +4,7 @@ import threading
4
4
  from typing import Any, Callable, Dict, List
5
5
  from urllib.parse import urljoin
6
6
 
7
- import requests
7
+ import requests # type: ignore[import-untyped]
8
8
 
9
9
  from jarvis.jarvis_mcp import McpClient
10
10
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -25,6 +25,8 @@ class StreamableMcpClient(McpClient):
25
25
  self.base_url = config.get("base_url", "")
26
26
  if not self.base_url:
27
27
  raise ValueError("No base_url specified in config")
28
+ # Normalize base_url to ensure trailing slash for urljoin correctness
29
+ self.base_url = self.base_url.rstrip("/") + "/"
28
30
 
29
31
  # 设置HTTP客户端
30
32
  self.session = requests.Session()
@@ -43,6 +45,8 @@ class StreamableMcpClient(McpClient):
43
45
  # 添加额外的HTTP头
44
46
  extra_headers = config.get("headers", {})
45
47
  self.session.headers.update(extra_headers)
48
+ # Request timeouts (connect, read) in seconds; can be overridden via config["timeout"]
49
+ self.timeout = config.get("timeout", (10, 300))
46
50
 
47
51
  # 请求相关属性
48
52
  self.pending_requests: Dict[str, threading.Event] = {} # 存储等待响应的请求 {id: Event}
@@ -141,7 +145,9 @@ class StreamableMcpClient(McpClient):
141
145
 
142
146
  # 发送请求到Streamable HTTP端点
143
147
  mcp_url = urljoin(self.base_url, "mcp")
144
- response = self.session.post(mcp_url, json=request, stream=True) # 启用流式传输
148
+ response = self.session.post(
149
+ mcp_url, json=request, stream=True, timeout=self.timeout
150
+ ) # 启用流式传输
145
151
  response.raise_for_status()
146
152
 
147
153
  # 处理流式响应
@@ -149,17 +155,21 @@ class StreamableMcpClient(McpClient):
149
155
  for line in response.iter_lines(decode_unicode=True):
150
156
  if line:
151
157
  try:
152
- data = json.loads(line)
158
+ line_data = line
159
+ if isinstance(line_data, str) and line_data.startswith("data:"):
160
+ # Handle SSE-formatted lines like "data: {...}"
161
+ line_data = line_data.split(":", 1)[1].strip()
162
+ data = json.loads(line_data)
153
163
  if "id" in data and data["id"] == req_id:
154
164
  # 这是我们的请求响应
155
165
  result = data
156
166
  break
157
167
  elif "method" in data:
158
168
  # 这是一个通知
159
- method = data.get("method", "")
169
+ notify_method = data.get("method", "")
160
170
  params = data.get("params", {})
161
- if method in self.notification_handlers:
162
- for handler in self.notification_handlers[method]:
171
+ if notify_method in self.notification_handlers:
172
+ for handler in self.notification_handlers[notify_method]:
163
173
  try:
164
174
  handler(params)
165
175
  except Exception as e:
@@ -171,6 +181,8 @@ class StreamableMcpClient(McpClient):
171
181
  PrettyOutput.print(f"无法解析响应: {line}", OutputType.WARNING)
172
182
  continue
173
183
 
184
+ # Ensure response is closed after streaming
185
+ response.close()
174
186
  if result is None:
175
187
  raise RuntimeError(f"未收到响应: {method}")
176
188
 
@@ -198,8 +210,9 @@ class StreamableMcpClient(McpClient):
198
210
 
199
211
  # 发送通知到Streamable HTTP端点
200
212
  mcp_url = urljoin(self.base_url, "mcp")
201
- response = self.session.post(mcp_url, json=notification)
213
+ response = self.session.post(mcp_url, json=notification, timeout=self.timeout)
202
214
  response.raise_for_status()
215
+ response.close()
203
216
 
204
217
  except Exception as e:
205
218
  PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)
@@ -2,7 +2,7 @@
2
2
  from typing import Optional
3
3
 
4
4
  import typer
5
- import yaml
5
+ import yaml # type: ignore[import-untyped]
6
6
 
7
7
  from jarvis.jarvis_multi_agent import MultiAgent
8
8
  from jarvis.jarvis_utils.input import get_multiline_input
@@ -15,7 +15,9 @@ app = typer.Typer(help="多智能体系统启动器")
15
15
  @app.command()
16
16
  def cli(
17
17
  config: str = typer.Option(..., "--config", "-c", help="YAML配置文件路径"),
18
- user_input: Optional[str] = typer.Option(None, "--input", "-i", help="用户输入(可选)"),
18
+ user_input: Optional[str] = typer.Option(
19
+ None, "--input", "-i", help="用户输入(可选)"
20
+ ),
19
21
  ):
20
22
  """从YAML配置文件初始化并运行多智能体系统"""
21
23
  init_env("欢迎使用 Jarvis-MultiAgent,您的多智能体系统已准备就绪!")
@@ -39,11 +41,13 @@ def cli(
39
41
  else get_multiline_input("请输入内容(输入空行结束):")
40
42
  )
41
43
  if not final_input:
42
- raise typer.Exit(code=0)
44
+ return
43
45
  multi_agent.run(final_input)
44
46
 
47
+ except KeyboardInterrupt:
48
+ return
45
49
  except typer.Exit:
46
- raise
50
+ return
47
51
  except (ValueError, RuntimeError, yaml.YAMLError) as e:
48
52
  PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
49
53
  raise typer.Exit(code=1)
@@ -7,6 +7,7 @@ import asyncio
7
7
  import json
8
8
  import os
9
9
  import time
10
+ import threading
10
11
  import uuid
11
12
  from datetime import datetime
12
13
  from typing import Any, Dict, List, Optional, Union
@@ -72,8 +73,16 @@ def start_service(
72
73
  ) -> None:
73
74
  """Start OpenAI-compatible API server."""
74
75
  # Create logs directory if it doesn't exist
75
- logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
76
- os.makedirs(logs_dir, exist_ok=True)
76
+ # Prefer environment variable, then user directory, fall back to CWD
77
+ logs_dir = os.environ.get("JARVIS_LOG_DIR")
78
+ if not logs_dir:
79
+ logs_dir = os.path.join(os.path.expanduser("~"), ".jarvis", "logs")
80
+ try:
81
+ os.makedirs(logs_dir, exist_ok=True)
82
+ except Exception:
83
+ # As a last resort, use current working directory
84
+ logs_dir = os.path.join(os.getcwd(), "logs")
85
+ os.makedirs(logs_dir, exist_ok=True)
77
86
 
78
87
  app = FastAPI(title="Jarvis API Server")
79
88
 
@@ -81,7 +90,7 @@ def start_service(
81
90
  app.add_middleware(
82
91
  CORSMiddleware,
83
92
  allow_origins=["*"],
84
- allow_credentials=True,
93
+ allow_credentials=False,
85
94
  allow_methods=["*"],
86
95
  allow_headers=["*"],
87
96
  )
@@ -228,23 +237,23 @@ def start_service(
228
237
  "messages": [{"role": m.role, "content": m.content} for m in messages],
229
238
  }
230
239
 
231
- # Log the conversation
232
- log_conversation(
233
- conversation_id,
234
- [{"role": m.role, "content": m.content} for m in messages],
235
- model,
236
- )
240
+ # Logging moved to post-response to avoid duplicates
237
241
 
238
242
  if stream:
239
243
  # Return streaming response
240
244
  return StreamingResponse(
241
245
  stream_chat_response(platform, message_text, model), # type: ignore
242
246
  media_type="text/event-stream",
247
+ headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
243
248
  )
244
249
 
245
250
  # Get chat response
246
251
  try:
247
- response_text = platform.chat_until_success(message_text)
252
+ # Run potentially blocking call in a thread to avoid blocking the event loop
253
+ loop = asyncio.get_running_loop()
254
+ response_text = await loop.run_in_executor(
255
+ None, lambda: platform.chat_until_success(message_text)
256
+ )
248
257
 
249
258
  # Create response in OpenAI format
250
259
  completion_id = f"chatcmpl-{str(uuid.uuid4())}"
@@ -287,11 +296,31 @@ def start_service(
287
296
  raise HTTPException(status_code=500, detail=str(exc))
288
297
 
289
298
  async def stream_chat_response(platform: Any, message: str, model_name: str) -> Any:
290
- """Stream chat response in OpenAI-compatible format."""
299
+ """Stream chat response in OpenAI-compatible format without blocking the event loop."""
291
300
  completion_id = f"chatcmpl-{str(uuid.uuid4())}"
292
301
  created_time = int(time.time())
293
302
  conversation_id = str(uuid.uuid4())
294
303
 
304
+ loop = asyncio.get_running_loop()
305
+ queue: asyncio.Queue = asyncio.Queue()
306
+ SENTINEL = object()
307
+
308
+ def producer() -> None:
309
+ try:
310
+ for chunk in platform.chat(message):
311
+ if chunk:
312
+ asyncio.run_coroutine_threadsafe(queue.put(chunk), loop)
313
+ except Exception as exc:
314
+ # Use a special dict to pass error across thread boundary
315
+ asyncio.run_coroutine_threadsafe(
316
+ queue.put({"__error__": str(exc)}), loop
317
+ )
318
+ finally:
319
+ asyncio.run_coroutine_threadsafe(queue.put(SENTINEL), loop)
320
+
321
+ # Start producer thread
322
+ threading.Thread(target=producer, daemon=True).start()
323
+
295
324
  # Send the initial chunk with the role
296
325
  initial_data = {
297
326
  "id": completion_id,
@@ -304,36 +333,20 @@ def start_service(
304
333
  }
305
334
  yield f"data: {json.dumps(initial_data)}\n\n"
306
335
 
307
- try:
308
- # Use the streaming-capable chat method
309
- response_generator = platform.chat(message)
310
-
311
- full_response = ""
312
- has_content = False
313
-
314
- # Iterate over the generator and stream chunks
315
- for chunk in response_generator:
316
- if chunk:
317
- has_content = True
318
- full_response += chunk
319
- chunk_data = {
320
- "id": completion_id,
321
- "object": "chat.completion.chunk",
322
- "created": created_time,
323
- "model": model_name,
324
- "choices": [
325
- {
326
- "index": 0,
327
- "delta": {"content": chunk},
328
- "finish_reason": None,
329
- }
330
- ],
331
- }
332
- yield f"data: {json.dumps(chunk_data)}\n\n"
336
+ full_response = ""
337
+ has_content = False
338
+
339
+ while True:
340
+ item = await queue.get()
341
+ if item is SENTINEL:
342
+ break
343
+
344
+ if isinstance(item, dict) and "__error__" in item:
345
+ error_msg = f"Error during streaming: {item['__error__']}"
346
+ PrettyOutput.print(error_msg, OutputType.ERROR)
333
347
 
334
- if not has_content:
335
- no_response_message = "No response from model."
336
- chunk_data = {
348
+ # Send error information in the stream
349
+ error_chunk = {
337
350
  "id": completion_id,
338
351
  "object": "chat.completion.chunk",
339
352
  "created": created_time,
@@ -341,41 +354,45 @@ def start_service(
341
354
  "choices": [
342
355
  {
343
356
  "index": 0,
344
- "delta": {"content": no_response_message},
345
- "finish_reason": None,
357
+ "delta": {"content": error_msg},
358
+ "finish_reason": "stop",
346
359
  }
347
360
  ],
348
361
  }
349
- yield f"data: {json.dumps(chunk_data)}\n\n"
350
- full_response = no_response_message
362
+ yield f"data: {json.dumps(error_chunk)}\n\n"
363
+ yield "data: [DONE]\n\n"
364
+
365
+ # Log the error
366
+ log_conversation(
367
+ conversation_id,
368
+ [{"role": "user", "content": message}],
369
+ model_name,
370
+ response=f"ERROR: {error_msg}",
371
+ )
372
+ return
351
373
 
352
- # Send the final chunk with finish_reason
353
- final_data = {
374
+ # Normal chunk
375
+ chunk = item
376
+ has_content = True
377
+ full_response += chunk
378
+ chunk_data = {
354
379
  "id": completion_id,
355
380
  "object": "chat.completion.chunk",
356
381
  "created": created_time,
357
382
  "model": model_name,
358
- "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
383
+ "choices": [
384
+ {
385
+ "index": 0,
386
+ "delta": {"content": chunk},
387
+ "finish_reason": None,
388
+ }
389
+ ],
359
390
  }
360
- yield f"data: {json.dumps(final_data)}\n\n"
391
+ yield f"data: {json.dumps(chunk_data)}\n\n"
361
392
 
362
- # Send the [DONE] marker
363
- yield "data: [DONE]\n\n"
364
-
365
- # Log the full conversation
366
- log_conversation(
367
- conversation_id,
368
- [{"role": "user", "content": message}],
369
- model_name,
370
- full_response,
371
- )
372
-
373
- except Exception as exc:
374
- error_msg = f"Error during streaming: {str(exc)}"
375
- PrettyOutput.print(error_msg, OutputType.ERROR)
376
-
377
- # Send error information in the stream
378
- error_chunk = {
393
+ if not has_content:
394
+ no_response_message = "No response from model."
395
+ chunk_data = {
379
396
  "id": completion_id,
380
397
  "object": "chat.completion.chunk",
381
398
  "created": created_time,
@@ -383,21 +400,34 @@ def start_service(
383
400
  "choices": [
384
401
  {
385
402
  "index": 0,
386
- "delta": {"content": error_msg},
387
- "finish_reason": "stop",
403
+ "delta": {"content": no_response_message},
404
+ "finish_reason": None,
388
405
  }
389
406
  ],
390
407
  }
391
- yield f"data: {json.dumps(error_chunk)}\n\n"
392
- yield "data: [DONE]\n\n"
408
+ yield f"data: {json.dumps(chunk_data)}\n\n"
409
+ full_response = no_response_message
393
410
 
394
- # Log the error
395
- log_conversation(
396
- conversation_id,
397
- [{"role": "user", "content": message}],
398
- model_name,
399
- response=f"ERROR: {error_msg}",
400
- )
411
+ # Send the final chunk with finish_reason
412
+ final_data = {
413
+ "id": completion_id,
414
+ "object": "chat.completion.chunk",
415
+ "created": created_time,
416
+ "model": model_name,
417
+ "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
418
+ }
419
+ yield f"data: {json.dumps(final_data)}\n\n"
420
+
421
+ # Send the [DONE] marker
422
+ yield "data: [DONE]\n\n"
423
+
424
+ # Log the full conversation
425
+ log_conversation(
426
+ conversation_id,
427
+ [{"role": "user", "content": message}],
428
+ model_name,
429
+ full_response,
430
+ )
401
431
 
402
432
  # Run the server
403
433
  uvicorn.run(app, host=host, port=port)
@@ -6,7 +6,14 @@ from typing import Any, Callable, Dict
6
6
  class Tool:
7
7
  """工具类,用于封装工具的基本信息和执行方法"""
8
8
 
9
- def __init__(self, name: str, description: str, parameters: Dict, func: Callable):
9
+ def __init__(
10
+ self,
11
+ name: str,
12
+ description: str,
13
+ parameters: Dict,
14
+ func: Callable,
15
+ protocol_version: str = "1.0",
16
+ ):
10
17
  """
11
18
  初始化工具对象
12
19
 
@@ -15,11 +22,13 @@ class Tool:
15
22
  description (str): 工具描述
16
23
  parameters (Dict): 工具参数定义
17
24
  func (Callable): 工具执行函数
25
+ protocol_version (str): 工具协议版本,默认"1.0";支持"1.0"或"2.0"
18
26
  """
19
27
  self.name = name
20
28
  self.description = description
21
29
  self.parameters = parameters
22
30
  self.func = func
31
+ self.protocol_version = protocol_version
23
32
 
24
33
  def to_dict(self) -> Dict:
25
34
  """将工具对象转换为字典格式,主要用于序列化"""
@@ -7,7 +7,7 @@ import tempfile
7
7
  from pathlib import Path
8
8
  from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple
9
9
 
10
- import yaml
10
+ import yaml # type: ignore[import-untyped]
11
11
 
12
12
  from jarvis.jarvis_mcp import McpClient
13
13
  from jarvis.jarvis_mcp.sse_mcp_client import SSEMcpClient
@@ -32,6 +32,7 @@ tool_call_help = f"""
32
32
  {ot("TOOL_CALL")}
33
33
  want: 想要从执行结果中获取到的信息,如果工具输出内容过长,会根据此字段尝试提取有效信息
34
34
  name: 工具名称
35
+
35
36
  arguments:
36
37
  param1: 值1
37
38
  param2: 值2
@@ -77,9 +78,10 @@ arguments:
77
78
  {ot("TOOL_CALL")}
78
79
  want: 当前的git状态,期望获取xxx的提交记录
79
80
  name: execute_script
81
+
80
82
  arguments:
81
83
  interpreter: bash
82
- script_cotent: |2
84
+ script_content: |2
83
85
  git status --porcelain
84
86
  {ct("TOOL_CALL")}
85
87
  </string_format>
@@ -595,6 +597,9 @@ class ToolRegistry(OutputHandlerProtocol):
595
597
  description=tool_instance.description,
596
598
  parameters=tool_instance.parameters,
597
599
  func=tool_instance.execute,
600
+ protocol_version=getattr(
601
+ tool_instance, "protocol_version", "1.0"
602
+ ),
598
603
  )
599
604
  tool_found = True
600
605
  break
@@ -630,7 +635,9 @@ class ToolRegistry(OutputHandlerProtocol):
630
635
  content: 包含工具调用的内容
631
636
 
632
637
  返回:
633
- List[Dict]: 包含名称和参数的提取工具调用列表
638
+ Tuple[Dict[str, Dict[str, Any]], str]:
639
+ - 第一个元素是提取的工具调用字典
640
+ - 第二个元素是错误消息字符串(成功时为"")
634
641
 
635
642
  异常:
636
643
  Exception: 如果工具调用缺少必要字段
@@ -706,6 +713,7 @@ class ToolRegistry(OutputHandlerProtocol):
706
713
  description: str,
707
714
  parameters: Any,
708
715
  func: Callable[..., Dict[str, Any]],
716
+ protocol_version: str = "1.0",
709
717
  ) -> None:
710
718
  """注册新工具
711
719
 
@@ -719,7 +727,7 @@ class ToolRegistry(OutputHandlerProtocol):
719
727
  PrettyOutput.print(
720
728
  f"警告: 工具 '{name}' 已存在,将被覆盖", OutputType.WARNING
721
729
  )
722
- self.tools[name] = Tool(name, description, parameters, func)
730
+ self.tools[name] = Tool(name, description, parameters, func, protocol_version)
723
731
 
724
732
  def get_tool(self, name: str) -> Optional[Tool]:
725
733
  """获取工具
@@ -740,12 +748,15 @@ class ToolRegistry(OutputHandlerProtocol):
740
748
  """
741
749
  return [tool.to_dict() for tool in self.tools.values()]
742
750
 
743
- def execute_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
751
+ def execute_tool(
752
+ self, name: str, arguments: Dict[str, Any], agent: Optional[Any] = None
753
+ ) -> Dict[str, Any]:
744
754
  """执行指定工具
745
755
 
746
756
  参数:
747
757
  name: 工具名称
748
758
  arguments: 工具参数
759
+ agent: 智能体实例(由系统内部传递,用于v2.0分离agent与参数)
749
760
 
750
761
  返回:
751
762
  Dict[str, Any]: 包含执行结果的字典,包含success、stdout和stderr字段
@@ -761,7 +772,23 @@ class ToolRegistry(OutputHandlerProtocol):
761
772
  # 更新工具调用统计
762
773
  self._update_tool_stats(name)
763
774
 
764
- return tool.execute(arguments)
775
+ # 根据工具实现声明的协议版本分发调用方式
776
+ try:
777
+ if getattr(tool, "protocol_version", "1.0") == "2.0":
778
+ # v2.0: agent与参数分离传递
779
+ return tool.func(arguments, agent) # type: ignore[misc]
780
+ else:
781
+ # v1.0: 兼容旧实现,将agent注入到arguments(如果提供)
782
+ args_to_call = arguments.copy() if isinstance(arguments, dict) else {}
783
+ if agent is not None:
784
+ args_to_call["agent"] = agent
785
+ return tool.execute(args_to_call)
786
+ except TypeError:
787
+ # 兼容处理:如果函数签名不匹配,回退到旧方式
788
+ args_to_call = arguments.copy() if isinstance(arguments, dict) else {}
789
+ if agent is not None:
790
+ args_to_call["agent"] = agent
791
+ return tool.execute(args_to_call)
765
792
 
766
793
  def _format_tool_output(self, stdout: str, stderr: str) -> str:
767
794
  """格式化工具输出为可读字符串
@@ -800,14 +827,14 @@ class ToolRegistry(OutputHandlerProtocol):
800
827
  def handle_tool_calls(self, tool_call: Dict[str, Any], agent: Any) -> str:
801
828
  try:
802
829
  name = tool_call["name"] # 确保name是str类型
803
- args = tool_call["arguments"] # args已经是Dict[str, Any]
830
+ args = tool_call["arguments"] # 原始参数(来自外部协议)
804
831
  want = tool_call["want"]
805
- args["agent"] = agent
806
832
 
807
833
  from jarvis.jarvis_agent import Agent
808
834
 
809
835
  agent_instance: Agent = agent
810
836
 
837
+ # 如果args是字符串,尝试解析为JSON
811
838
  if isinstance(args, str):
812
839
  try:
813
840
  args = json.loads(args)
@@ -817,8 +844,8 @@ class ToolRegistry(OutputHandlerProtocol):
817
844
  )
818
845
  return ""
819
846
 
820
- # 执行工具调用
821
- result = self.execute_tool(name, args) # 修正参数传递
847
+ # 执行工具调用(根据工具实现的协议版本,由系统在内部决定agent的传递方式)
848
+ result = self.execute_tool(name, args, agent)
822
849
 
823
850
  # 格式化输出
824
851
  output = self._format_tool_output(
@@ -851,8 +878,9 @@ class ToolRegistry(OutputHandlerProtocol):
851
878
  [output_file]
852
879
  )
853
880
  if upload_success:
854
- # 删除args的agent
855
- args.pop("agent", None)
881
+ # 删除args的agent键(保持协议v2.0的“参数与agent分离”在可视化中的一致性)
882
+ if isinstance(args, dict):
883
+ args.pop("agent", None)
856
884
  prompt = f"""
857
885
  以下是之前对话的关键信息总结:
858
886
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.3.14
3
+ Version: 0.3.16
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire
@@ -1,12 +1,12 @@
1
- jarvis/__init__.py,sha256=VWGr6Ejq9BZqFLJznrElHDghpbCaBnkgpyMgrmU4ysk,74
1
+ jarvis/__init__.py,sha256=5JlEGGjW7DTnB2ahsKZhnkQHtdqbBlTqmnALZllbEWw,74
2
2
  jarvis/jarvis_agent/__init__.py,sha256=CkFa66l5lM0-_zlzApwBxTYbrnbC4_NqdD4QuK3H1VQ,32614
3
3
  jarvis/jarvis_agent/agent_manager.py,sha256=YzpMiF0H2-eyk2kn2o24Bkj3bXsQx7Pv2vfD4gWepo0,2893
4
4
  jarvis/jarvis_agent/builtin_input_handler.py,sha256=wS-FqpT3pIXwHn1dfL3SpXonUKWgVThbQueUIeyRc2U,2917
5
5
  jarvis/jarvis_agent/config_editor.py,sha256=Ctk82sO6w2cNW0-_5L7Bomj-hgM4U7WwMc52fwhAJyg,1809
6
6
  jarvis/jarvis_agent/edit_file_handler.py,sha256=w-byNJ4TN_SlV3djjfFC7OksySOFGrM8ku49w662dzc,11854
7
7
  jarvis/jarvis_agent/file_methodology_manager.py,sha256=PwDUQwq7HVIyPInsN8fgWyMXLwi8heIXPrqfBZJhVHs,4260
8
- jarvis/jarvis_agent/jarvis.py,sha256=mE7sPQzuHtL9j8e_yDO91KfpbEqxrBrBG3px_WyWz50,3238
9
- jarvis/jarvis_agent/main.py,sha256=0xq-rjadcTedcB2HJSk2v0ihcm2-r2NXLj_Nq9xS9LY,3345
8
+ jarvis/jarvis_agent/jarvis.py,sha256=wi5V-pIMoXuWMcd1JgZ9G912SHnbabrF6osAK-ClcDk,11524
9
+ jarvis/jarvis_agent/main.py,sha256=Hu5u0mq0owuzt965IqaGP6TtVGFXHE4E4Tg1TzCtGYE,3552
10
10
  jarvis/jarvis_agent/memory_manager.py,sha256=F7HTNzdN1_-cSygnz7zKSJRJvPLUOosqcXQeiW8zG4U,5266
11
11
  jarvis/jarvis_agent/methodology_share_manager.py,sha256=vwWNexluTXSI3qeNP3zJAemOjWW37o_1AlqDR1C8wCI,6910
12
12
  jarvis/jarvis_agent/output_handler.py,sha256=P7oWpXBGFfOsWq7cIhS_z9crkQ19ES7qU5pM92KKjAs,1172
@@ -21,7 +21,7 @@ jarvis/jarvis_agent/task_manager.py,sha256=HJm4_SMpsFbQMUUsAZeHm7cZuhNbz28YW-DRL
21
21
  jarvis/jarvis_agent/tool_executor.py,sha256=k73cKhZEZpljvui4ZxALlFEIE-iLzJ32Softsmiwzqk,1896
22
22
  jarvis/jarvis_agent/tool_share_manager.py,sha256=R5ONIQlDXX9pFq3clwHFhEW8BAJ3ECaR2DqWCEC9tzM,5205
23
23
  jarvis/jarvis_code_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- jarvis/jarvis_code_agent/code_agent.py,sha256=GPmCMlAERHC3yPy0LEv3ATdPsZm99MUz1XxcNB9_p2o,29972
24
+ jarvis/jarvis_code_agent/code_agent.py,sha256=GhY79JRzyLeeNKxNJtgieO9UvqQl36S7W521ZyOwjMY,30967
25
25
  jarvis/jarvis_code_agent/lint.py,sha256=LZPsfyZPMo7Wm7LN4osZocuNJwZx1ojacO3MlF870x8,4009
26
26
  jarvis/jarvis_code_analysis/code_review.py,sha256=OLoMtXz7Kov6cVTdBoxq_OsX_j0rb7Rk3or5tKgiLpo,36023
27
27
  jarvis/jarvis_code_analysis/checklists/__init__.py,sha256=LIXAYa1sW3l7foP6kohLWnE98I_EQ0T7z5bYKHq6rJA,78
@@ -52,12 +52,12 @@ jarvis/jarvis_git_utils/git_commiter.py,sha256=GpSnVa72b9yWoJBbK1Qp_Kb4iimwVW6K7
52
52
  jarvis/jarvis_mcp/__init__.py,sha256=OPMtjD-uq9xAaKCRIDyKIosaFfBe1GBPu1az-mQ0rVM,2048
53
53
  jarvis/jarvis_mcp/sse_mcp_client.py,sha256=neKrgFxwLDPWjVrl9uDt1ricNwbLZbv1ZEFh0IkmqZk,22656
54
54
  jarvis/jarvis_mcp/stdio_mcp_client.py,sha256=APYUksYKlMx7AVNODKOLrTkKZPnp4kqTQIYIuNDDKko,11286
55
- jarvis/jarvis_mcp/streamable_mcp_client.py,sha256=sP0KEsxVcXGht0eA7a_m-ECtZAk39s4PL9OUdm35x2Y,14467
55
+ jarvis/jarvis_mcp/streamable_mcp_client.py,sha256=P5keAhI7SsVjAq3nU9J7pp2Tk4pJDxjdPAb6ZcVPLEc,15279
56
56
  jarvis/jarvis_memory_organizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  jarvis/jarvis_memory_organizer/memory_organizer.py,sha256=4tf6Bs8u6Drj4repvuY3-XeH2Sb6ajVMFcW-rQEiGEY,26502
58
58
  jarvis/jarvis_methodology/main.py,sha256=6QF8hH3vB6rfxim0fPR34uVPf41zVpb4ZLqrFN2qONg,10983
59
59
  jarvis/jarvis_multi_agent/__init__.py,sha256=kCgtAX7VvliyEOQxIj2DvNjRAuh6bpNaOtDn60nzph4,6089
60
- jarvis/jarvis_multi_agent/main.py,sha256=Wbarez48QxXexlKEOcRsoMbcQEOP5rv_DzGkNk0SfpY,1779
60
+ jarvis/jarvis_multi_agent/main.py,sha256=b9IThFMeUZCYSlgT-VT8r7xeBdrEE_zNT11awEc8IdY,1853
61
61
  jarvis/jarvis_platform/__init__.py,sha256=WLQHSiE87PPket2M50_hHzjdMIgPIBx2VF8JfB_NNRk,105
62
62
  jarvis/jarvis_platform/ai8.py,sha256=W3947AGMpk3RRBfsfZmf222sEP0VIGoSU0vPkgiVnl0,11683
63
63
  jarvis/jarvis_platform/base.py,sha256=VyZhXgtGbUTrhXaDwA3kYUo03mfM-nNCYaTGgpY_TZQ,9365
@@ -69,7 +69,7 @@ jarvis/jarvis_platform/tongyi.py,sha256=KXEMfylTU91kHisXSaiz8dxzNXK_d7XD9vjuw4yX
69
69
  jarvis/jarvis_platform/yuanbao.py,sha256=32hjk1Ju1tqrMpF47JsSuaxej5K-gUPxjsDu9g0briY,23575
70
70
  jarvis/jarvis_platform_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  jarvis/jarvis_platform_manager/main.py,sha256=5k7D-tBsNjXPH07eO4f-0gwyUY7STGSNBSl1PbLq15A,20966
72
- jarvis/jarvis_platform_manager/service.py,sha256=myJYGSUclCEiRTf3JKs4JndwhXJeQj7MQQy4i13jMt0,13767
72
+ jarvis/jarvis_platform_manager/service.py,sha256=DnuRJjD7RvunGt3LpMfUDr-Bps-Nb--frkeaC0nwxj0,14874
73
73
  jarvis/jarvis_rag/__init__.py,sha256=HRTXgnQxDuaE9x-e3r6SYqhJ5d4DSI_rrIxy2IGY6qk,320
74
74
  jarvis/jarvis_rag/cache.py,sha256=Tqx_Oe-AhuWlMXHGHUaIuG6OEHoHBVZq7mL3kldtFFU,2723
75
75
  jarvis/jarvis_rag/cli.py,sha256=bIQKibp8swJDyfFBXaiX5C20LHN_2W2knO2I-MQp58c,15620
@@ -88,7 +88,7 @@ jarvis/jarvis_stats/storage.py,sha256=MBQRxExIWdePXzY1EE8JAs1IEpMqamImpgjruqt_u9
88
88
  jarvis/jarvis_stats/visualizer.py,sha256=ZIBmGELzs6c7qM01tQql1HF6eFKn6HDGVQfKXRUUIY0,8529
89
89
  jarvis/jarvis_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  jarvis/jarvis_tools/ask_user.py,sha256=M6DdLNryCE8y1JcdZHEifUgZkPUEPNKc-zDW5p0Mb1k,2029
91
- jarvis/jarvis_tools/base.py,sha256=kgDjh64HYr_ox_1FR7j52LsJ4B4W2wSqOejdtSBb2Fc,1413
91
+ jarvis/jarvis_tools/base.py,sha256=tFZkRlbV_a-pbjM-ci9AYmXVJm__FXuzVWKbQEyz4Ao,1639
92
92
  jarvis/jarvis_tools/clear_memory.py,sha256=HQMK70UJhhDgPPHozGaTpYizzQblUzYRwPbvD1z3z6o,8730
93
93
  jarvis/jarvis_tools/edit_file.py,sha256=hM345E9rxS-EkqCZpwwizL6fmPdTadtB798tEO5Ce3g,10417
94
94
  jarvis/jarvis_tools/execute_script.py,sha256=gMarE5yCCSPU6Dp6HlcL2KT-2xCzR-1p-oQNlYOJK58,6157
@@ -97,7 +97,7 @@ jarvis/jarvis_tools/generate_new_tool.py,sha256=uaWKlDMGjetvvwKTj0_AVTdmd14IktRb
97
97
  jarvis/jarvis_tools/methodology.py,sha256=_K4GIDUodGEma3SvNRo7Qs5rliijgNespVLyAPN35JU,5233
98
98
  jarvis/jarvis_tools/read_code.py,sha256=EnI-R-5HyIQYhMD391nZWXHIuHHBF-OJIRE0QpLcPX4,6417
99
99
  jarvis/jarvis_tools/read_webpage.py,sha256=NmDUboVZd4CGHBPRFK6dp3uqVhuGopW1bOi3TcaLDF4,2092
100
- jarvis/jarvis_tools/registry.py,sha256=TtZ415LUMfWqfcgn3G5V4e3QLLU2ILNRatkP10U0Ypw,31047
100
+ jarvis/jarvis_tools/registry.py,sha256=IJ2ExlNqje2hA9MgiGnd0SCyy0zqAhxuZ76s_98FGwU,32668
101
101
  jarvis/jarvis_tools/retrieve_memory.py,sha256=0UBZm4wQTXLTj5WHXR9fjsiIDQh-Z2UINVu8cJ12YYg,9488
102
102
  jarvis/jarvis_tools/rewrite_file.py,sha256=eG_WKg6cVAXmuGwUqlWkcuyay5S8DOzEi8vZCmX3O8w,7255
103
103
  jarvis/jarvis_tools/save_memory.py,sha256=DjeFb38OtK9Y_RpWYHz8vL72JdauXZTlc_Y0FUQBtiM,7486
@@ -119,9 +119,9 @@ jarvis/jarvis_utils/methodology.py,sha256=IIMU17WVSunsWXsnXROd4G77LxgYs4xEC_xm_0
119
119
  jarvis/jarvis_utils/output.py,sha256=QRLlKObQKT0KuRSeZRqYb7NlTQvsd1oZXZ41WxeWEuU,10894
120
120
  jarvis/jarvis_utils/tag.py,sha256=f211opbbbTcSyzCDwuIK_oCnKhXPNK-RknYyGzY1yD0,431
121
121
  jarvis/jarvis_utils/utils.py,sha256=LiVui9RMsbfUdzbvBBwbGNC4uniGnLp3LFsk7LXGrQE,47370
122
- jarvis_ai_assistant-0.3.14.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
123
- jarvis_ai_assistant-0.3.14.dist-info/METADATA,sha256=jPFjzOOpjbCDOcVVwpNJCWax51_2VcfMRg3AnuGnbHo,18216
124
- jarvis_ai_assistant-0.3.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
- jarvis_ai_assistant-0.3.14.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
126
- jarvis_ai_assistant-0.3.14.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
127
- jarvis_ai_assistant-0.3.14.dist-info/RECORD,,
122
+ jarvis_ai_assistant-0.3.16.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
123
+ jarvis_ai_assistant-0.3.16.dist-info/METADATA,sha256=wawbsyujbiRROXrHycIuMhIZsvp2hbt3UT5Mco3xPw4,18216
124
+ jarvis_ai_assistant-0.3.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
+ jarvis_ai_assistant-0.3.16.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
126
+ jarvis_ai_assistant-0.3.16.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
127
+ jarvis_ai_assistant-0.3.16.dist-info/RECORD,,