jarvis-ai-assistant 0.3.31__py3-none-any.whl → 0.3.33__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.31"
4
+ __version__ = "0.3.33"
@@ -12,6 +12,7 @@ from jarvis.jarvis_agent import (
12
12
  origin_agent_system_prompt,
13
13
  )
14
14
  from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
15
+ from jarvis.jarvis_agent.file_context_handler import file_context_handler
15
16
  from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
16
17
  from jarvis.jarvis_agent.task_manager import TaskManager
17
18
  from jarvis.jarvis_tools.registry import ToolRegistry
@@ -46,7 +47,11 @@ class AgentManager:
46
47
  self.agent = Agent(
47
48
  system_prompt=origin_agent_system_prompt,
48
49
  model_group=self.model_group,
49
- input_handler=[shell_input_handler, builtin_input_handler],
50
+ input_handler=[
51
+ shell_input_handler,
52
+ file_context_handler,
53
+ builtin_input_handler,
54
+ ],
50
55
  output_handler=[ToolRegistry()], # type: ignore
51
56
  need_summary=False,
52
57
  use_methodology=self.use_methodology,
@@ -166,7 +166,7 @@ class EditFileHandler(OutputHandler):
166
166
  - {supported_formats}
167
167
  - {ot("RANGE")}start-end{ct("RANGE")} 仅用于区间替换模式(SEARCH_START/SEARCH_END),表示只在指定行号范围内进行匹配与替换(1-based,闭区间);省略则在整个文件范围内处理
168
168
  - 单点替换要求 SEARCH 在有效范围内唯一匹配(仅替换第一个匹配)
169
- - 区间替换命中有效范围内的第一个 {ot("SEARCH_START")} 及其后的第一个 {ot("SEARCH_END")}
169
+ - 区间替换会从包含 {ot("SEARCH_START")} 的行首开始,到包含 {ot("SEARCH_END")} 的行尾结束,替换整个区域
170
170
  否则编辑将失败。"""
171
171
 
172
172
  def name(self) -> str:
@@ -468,24 +468,42 @@ class EditFileHandler(OutputHandler):
468
468
  error_msg = "未找到SEARCH_START"
469
469
  failed_patches.append({"patch": patch, "error": error_msg})
470
470
  else:
471
- end_idx = base_content.find(search_end, start_idx)
471
+ # search_start 之后开始查找 search_end
472
+ end_idx = base_content.find(search_end, start_idx + len(search_start))
472
473
  if end_idx == -1:
473
474
  error_msg = "在SEARCH_START之后未找到SEARCH_END"
474
475
  failed_patches.append({"patch": patch, "error": error_msg})
475
476
  else:
476
- # 避免额外空行:
477
- # REPLACE 以换行结尾且 SEARCH_END 后紧跟换行符,
478
- # 则将该换行并入替换范围,防止出现双重换行导致“多一行”
479
- end_of_range = end_idx + len(search_end)
477
+ # 将替换范围扩展到整行
478
+ # 找到 start_idx 所在行的行首
479
+ line_start_idx = base_content.rfind("\n", 0, start_idx) + 1
480
+
481
+ # 找到 end_idx 所在行的行尾
482
+ match_end_pos = end_idx + len(search_end)
483
+ line_end_idx = base_content.find("\n", match_end_pos)
484
+
485
+ if line_end_idx == -1:
486
+ # 如果没有找到换行符,说明是最后一行
487
+ end_of_range = len(base_content)
488
+ else:
489
+ # 包含换行符
490
+ end_of_range = line_end_idx + 1
491
+
492
+ final_replace_text = replace_text
493
+ original_slice = base_content[line_start_idx:end_of_range]
494
+
495
+ # 如果原始片段以换行符结尾,且替换内容不为空且不以换行符结尾,
496
+ # 则为替换内容添加换行符以保持格式
480
497
  if (
481
- end_of_range < len(base_content)
482
- and base_content[end_of_range] == "\n"
483
- and replace_text.endswith("\n")
498
+ final_replace_text
499
+ and original_slice.endswith("\n")
500
+ and not final_replace_text.endswith("\n")
484
501
  ):
485
- end_of_range += 1
502
+ final_replace_text += "\n"
503
+
486
504
  base_content = (
487
- base_content[:start_idx]
488
- + replace_text
505
+ base_content[:line_start_idx]
506
+ + final_replace_text
489
507
  + base_content[end_of_range:]
490
508
  )
491
509
  found = True
@@ -0,0 +1,69 @@
1
+ # -*- coding: utf-8 -*-
2
+ import re
3
+ import os
4
+ from typing import Any, Tuple
5
+
6
+ from jarvis.jarvis_tools.read_code import ReadCodeTool
7
+
8
+
9
+ def is_text_file(filepath: str) -> bool:
10
+ """
11
+ Check if a file is a text file.
12
+ """
13
+ try:
14
+ with open(filepath, "r", encoding="utf-8") as f:
15
+ f.read(1024) # Try to read a small chunk
16
+ return True
17
+ except (UnicodeDecodeError, IOError):
18
+ return False
19
+
20
+
21
+ def count_lines(filepath: str) -> int:
22
+ """
23
+ Count the number of lines in a file.
24
+ """
25
+ try:
26
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
27
+ return sum(1 for _ in f)
28
+ except IOError:
29
+ return 0
30
+
31
+
32
+ def file_context_handler(user_input: str, agent_: Any) -> Tuple[str, bool]:
33
+ """
34
+ Extracts file paths from the input, reads their content if they are valid text files
35
+ and appends the content to the input.
36
+
37
+ Args:
38
+ user_input: The user's input string.
39
+ agent_: The agent instance.
40
+
41
+ Returns:
42
+ A tuple containing the modified user input and a boolean indicating if
43
+ further processing should be skipped.
44
+ """
45
+ # Regex to find paths in single quotes
46
+ file_paths = re.findall(r"'([^']+)'", user_input)
47
+
48
+ if not file_paths:
49
+ return user_input, False
50
+
51
+ added_context = ""
52
+ read_code_tool = ReadCodeTool()
53
+
54
+ for path in file_paths:
55
+ if os.path.isfile(path) and is_text_file(path):
56
+ line_count = count_lines(path)
57
+ if line_count > 0:
58
+ # Use ReadCodeTool to get formatted content
59
+ result = read_code_tool._handle_single_file(path)
60
+ if result["success"]:
61
+ # Remove the file path from the original input to avoid redundancy
62
+ user_input = user_input.replace(f"'{path}'", "")
63
+ # Append the full, formatted output from the tool, which includes headers and line numbers
64
+ added_context += "\n" + result["stdout"]
65
+
66
+ if added_context:
67
+ user_input = user_input.strip() + added_context
68
+
69
+ return user_input, False
@@ -1,6 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI 助手主入口模块"""
3
3
  from typing import Optional, List
4
+ import shutil
5
+ from datetime import datetime
4
6
 
5
7
  import typer
6
8
 
@@ -16,6 +18,7 @@ from jarvis.jarvis_utils.config import (
16
18
  get_agent_definition_dirs,
17
19
  get_multi_agent_dirs,
18
20
  get_roles_dirs,
21
+ get_data_dir,
19
22
  )
20
23
  import jarvis.jarvis_utils.utils as jutils
21
24
  from jarvis.jarvis_utils.input import user_confirm, get_single_line_input
@@ -184,6 +187,91 @@ def handle_interactive_config_option(
184
187
  return True
185
188
 
186
189
 
190
+ def handle_backup_option(backup: bool) -> bool:
191
+ """处理数据备份选项,返回是否已处理并需提前结束。"""
192
+ if not backup:
193
+ return False
194
+
195
+ init_env("", config_file=None)
196
+ data_dir = Path(get_data_dir())
197
+ if not data_dir.is_dir():
198
+ PrettyOutput.print(f"数据目录不存在: {data_dir}", OutputType.ERROR)
199
+ return True
200
+
201
+ backup_dir = Path(os.path.expanduser("~/jarvis_backups"))
202
+ backup_dir.mkdir(exist_ok=True)
203
+
204
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
205
+ backup_file_base = backup_dir / f"jarvis_data_{timestamp}"
206
+
207
+ try:
208
+ archive_path = shutil.make_archive(
209
+ str(backup_file_base), "zip", root_dir=str(data_dir)
210
+ )
211
+ PrettyOutput.print(f"数据已成功备份到: {archive_path}", OutputType.SUCCESS)
212
+ except Exception as e:
213
+ PrettyOutput.print(f"数据备份失败: {e}", OutputType.ERROR)
214
+
215
+ return True
216
+
217
+
218
+ def handle_restore_option(restore_path: Optional[str], config_file: Optional[str]) -> bool:
219
+ """处理数据恢复选项,返回是否已处理并需提前结束。"""
220
+ if not restore_path:
221
+ return False
222
+
223
+ restore_file = Path(os.path.expanduser(os.path.expandvars(restore_path)))
224
+ # 兼容 ~ 与环境变量,避免用户输入未展开路径导致找不到文件
225
+ if not restore_file.is_file():
226
+ PrettyOutput.print(f"指定的恢复文件不存在: {restore_file}", OutputType.ERROR)
227
+ return True
228
+
229
+ # 在恢复数据时不要触发完整环境初始化,避免引导流程或网络请求
230
+ # 优先从配置文件解析 JARVIS_DATA_PATH,否则回退到默认数据目录
231
+ data_dir_str: Optional[str] = None
232
+ try:
233
+ if config_file:
234
+ cfg_path = Path(os.path.expanduser(os.path.expandvars(config_file)))
235
+ if cfg_path.is_file():
236
+ with open(cfg_path, "r", encoding="utf-8", errors="ignore") as cf:
237
+ cfg_data = yaml.safe_load(cf) or {}
238
+ if isinstance(cfg_data, dict):
239
+ val = cfg_data.get("JARVIS_DATA_PATH")
240
+ if isinstance(val, str) and val.strip():
241
+ data_dir_str = val.strip()
242
+ except Exception:
243
+ data_dir_str = None
244
+
245
+ if not data_dir_str:
246
+ data_dir_str = get_data_dir()
247
+
248
+ data_dir = Path(os.path.expanduser(os.path.expandvars(str(data_dir_str))))
249
+
250
+ if data_dir.exists():
251
+ if not user_confirm(
252
+ f"数据目录 '{data_dir}' 已存在,恢复操作将覆盖它。是否继续?", default=False
253
+ ):
254
+ PrettyOutput.print("恢复操作已取消。", OutputType.INFO)
255
+ return True
256
+ try:
257
+ shutil.rmtree(data_dir)
258
+ except Exception as e:
259
+ PrettyOutput.print(f"无法移除现有数据目录: {e}", OutputType.ERROR)
260
+ return True
261
+
262
+ try:
263
+ data_dir.mkdir(parents=True)
264
+ shutil.unpack_archive(str(restore_file), str(data_dir), "zip")
265
+ PrettyOutput.print(
266
+ f"数据已从 '{restore_path}' 成功恢复到 '{data_dir}'", OutputType.SUCCESS
267
+ )
268
+
269
+ except Exception as e:
270
+ PrettyOutput.print(f"数据恢复失败: {e}", OutputType.ERROR)
271
+
272
+ return True
273
+
274
+
187
275
  def preload_config_for_flags(config_file: Optional[str]) -> None:
188
276
  """预加载配置(仅用于读取功能开关),不会显示欢迎信息或影响后续 init_env。"""
189
277
  try:
@@ -374,6 +462,20 @@ def handle_builtin_config_selector(
374
462
  )
375
463
 
376
464
  if options:
465
+ # Add a default option to skip selection
466
+ options.insert(
467
+ 0,
468
+ {
469
+ "category": "skip",
470
+ "cmd": "",
471
+ "file": "",
472
+ "name": "跳过选择 (使用默认通用代理)",
473
+ "desc": "直接按回车或ESC也可跳过",
474
+ "details": "",
475
+ "roles_count": 0,
476
+ },
477
+ )
478
+
377
479
  PrettyOutput.section("可用的内置配置", OutputType.SUCCESS)
378
480
  # 使用 rich Table 呈现
379
481
  table = Table(show_header=True, header_style="bold magenta")
@@ -442,35 +544,44 @@ def handle_builtin_config_selector(
442
544
  if choice_index != -1:
443
545
  try:
444
546
  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)
547
+ # If the "skip" option is chosen, do nothing and proceed to default agent
548
+ if sel["category"] == "skip":
549
+ pass
550
+ else:
551
+ args: List[str] = []
552
+
553
+ if sel["category"] == "agent":
554
+ # jarvis-agent 支持 -f/--config(全局配置)与 -c/--agent-definition
555
+ args = [str(sel["cmd"]), "-c", str(sel["file"])]
556
+ if model_group:
557
+ args += ["-g", str(model_group)]
558
+ if config_file:
559
+ args += ["-f", str(config_file)]
560
+ if task:
561
+ args += ["--task", str(task)]
562
+
563
+ elif sel["category"] == "multi_agent":
564
+ # jarvis-multi-agent 需要 -c/--config,用户输入通过 -i/--input 传递
565
+ args = [str(sel["cmd"]), "-c", str(sel["file"])]
566
+ if task:
567
+ args += ["-i", str(task)]
568
+
569
+ elif sel["category"] == "roles":
570
+ # jarvis-platform-manager role 子命令,支持 -c/-t/-g
571
+ args = [
572
+ str(sel["cmd"]),
573
+ "role",
574
+ "-c",
575
+ str(sel["file"]),
576
+ ]
577
+ if model_group:
578
+ args += ["-g", str(model_group)]
579
+
580
+ if args:
581
+ PrettyOutput.print(
582
+ f"正在启动: {' '.join(args)}", OutputType.INFO
583
+ )
584
+ os.execvp(args[0], args)
474
585
  except Exception:
475
586
  # 任何异常都不影响默认流程
476
587
  pass
@@ -521,6 +632,12 @@ def run_cli(
521
632
  "--disable-methodology-analysis",
522
633
  help="禁用方法论和任务分析(覆盖配置文件设置)",
523
634
  ),
635
+ backup_data: bool = typer.Option(
636
+ False, "--backup-data", help="备份 Jarvis 数据目录 (~/.jarvis)"
637
+ ),
638
+ restore_data: Optional[str] = typer.Option(
639
+ None, "--restore-data", help="从指定的压缩包恢复 Jarvis 数据"
640
+ ),
524
641
  ) -> None:
525
642
  """Jarvis AI assistant command-line interface."""
526
643
  if ctx.invoked_subcommand is not None:
@@ -529,6 +646,14 @@ def run_cli(
529
646
  # 使用 rich 输出命令与快捷方式总览
530
647
  print_commands_overview()
531
648
 
649
+ # 处理数据备份
650
+ if handle_backup_option(backup_data):
651
+ return
652
+
653
+ # 处理数据恢复
654
+ if handle_restore_option(restore_data, config_file):
655
+ return
656
+
532
657
  # 处理配置文件编辑
533
658
  if handle_edit_option(edit, config_file):
534
659
  return
@@ -7,6 +7,7 @@ AgentRunLoop: 承载 Agent 的主运行循环逻辑。
7
7
  - 暂不变更外部调用入口,后续在 Agent._main_loop 中委派到该类
8
8
  - 保持与现有异常处理、工具调用、用户交互完全一致
9
9
  """
10
+ import os
10
11
  from enum import Enum
11
12
  from typing import Any, TYPE_CHECKING
12
13
 
@@ -22,6 +23,8 @@ if TYPE_CHECKING:
22
23
  class AgentRunLoop:
23
24
  def __init__(self, agent: "Agent") -> None:
24
25
  self.agent = agent
26
+ self.conversation_rounds = 0
27
+ self.tool_reminder_rounds = int(os.environ.get("JARVIS_TOOL_REMINDER_ROUNDS", 20))
25
28
 
26
29
  def run(self) -> Any:
27
30
  """主运行循环(委派到传入的 agent 实例的方法与属性)"""
@@ -29,6 +32,12 @@ class AgentRunLoop:
29
32
 
30
33
  while True:
31
34
  try:
35
+ self.conversation_rounds += 1
36
+ if self.conversation_rounds % self.tool_reminder_rounds == 0:
37
+ self.agent.session.addon_prompt = join_prompts(
38
+ [self.agent.session.addon_prompt, self.agent.get_tool_usage_prompt()]
39
+ )
40
+
32
41
  ag = self.agent
33
42
 
34
43
  # 更新输入处理器标志
@@ -14,6 +14,7 @@ import typer
14
14
  from jarvis.jarvis_agent import Agent
15
15
  from jarvis.jarvis_agent.events import AFTER_TOOL_CALL
16
16
  from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
17
+ from jarvis.jarvis_agent.file_context_handler import file_context_handler
17
18
  from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
18
19
  from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
19
20
  from jarvis.jarvis_code_agent.lint import get_lint_tools
@@ -89,7 +90,11 @@ class CodeAgent:
89
90
  auto_complete=False,
90
91
  output_handler=[tool_registry, EditFileHandler()], # type: ignore
91
92
  model_group=model_group,
92
- input_handler=[shell_input_handler, builtin_input_handler],
93
+ input_handler=[
94
+ shell_input_handler,
95
+ file_context_handler,
96
+ builtin_input_handler,
97
+ ],
93
98
  need_summary=need_summary,
94
99
  use_methodology=False, # 禁用方法论
95
100
  use_analysis=False, # 禁用分析
@@ -312,6 +312,11 @@
312
312
  "description": "是否启用立即中断:在对话迭代中检测到中断信号时立即返回",
313
313
  "default": false
314
314
  },
315
+ "JARVIS_SAVE_SESSION_HISTORY": {
316
+ "type": "boolean",
317
+ "description": "是否保存会话记录",
318
+ "default": false
319
+ },
315
320
  "JARVIS_GIT_CHECK_MODE": {
316
321
  "type": "string",
317
322
  "enum": ["strict", "warn"],
@@ -154,9 +154,30 @@ content: |2
154
154
  PrettyOutput.print(f"未知消息类型: {type(msg)}", OutputType.WARNING)
155
155
  break
156
156
 
157
+ # Generate a brief summary via direct model call to avoid run-loop recursion
158
+ try:
159
+ # 参照 Agent.generate_summary 的实现思路:基于当前 session.prompt 追加请求提示,直接调用底层模型
160
+ multi_agent_summary_prompt = """
161
+ 请基于当前会话,为即将发送给其他智能体的协作交接写一段摘要,包含:
162
+ - 已完成的主要工作与产出
163
+ - 关键决策及其理由
164
+ - 已知的约束/风险/边界条件
165
+ - 未解决的问题与待澄清点
166
+ - 下一步建议与对目标智能体的具体请求
167
+ 要求:
168
+ - 仅输出纯文本,不包含任何指令或工具调用
169
+ - 使用简洁的要点式表述
170
+ """.strip()
171
+ summary_any: Any = agent.model.chat_until_success( # type: ignore[attr-defined]
172
+ f"{agent.session.prompt}\n{multi_agent_summary_prompt}"
173
+ )
174
+ summary_text = summary_any.strip() if isinstance(summary_any, str) else ""
175
+ except Exception:
176
+ summary_text = ""
157
177
  prompt = f"""
158
178
  Please handle this message:
159
179
  from: {last_agent_name}
180
+ summary_of_sender_work: {summary_text}
160
181
  content: {msg['content']}
161
182
  """
162
183
  to_agent_name = msg.get("to")
@@ -1,5 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import re
3
+ import os
4
+ from datetime import datetime
3
5
  from abc import ABC, abstractmethod
4
6
  from types import TracebackType
5
7
  from typing import Dict, Generator, List, Optional, Tuple, Type
@@ -17,6 +19,8 @@ from jarvis.jarvis_utils.config import (
17
19
  get_pretty_output,
18
20
  is_print_prompt,
19
21
  is_immediate_abort,
22
+ is_save_session_history,
23
+ get_data_dir,
20
24
  )
21
25
  from jarvis.jarvis_utils.embedding import split_text_into_chunks
22
26
  from jarvis.jarvis_utils.globals import set_in_chat, get_interrupt, console
@@ -34,6 +38,7 @@ class BasePlatform(ABC):
34
38
  self.web = False # 添加web属性,默认false
35
39
  self._saved = False
36
40
  self.model_group: Optional[str] = None
41
+ self._session_history_file: Optional[str] = None
37
42
 
38
43
  def __enter__(self) -> Self:
39
44
  """Enter context manager"""
@@ -57,6 +62,7 @@ class BasePlatform(ABC):
57
62
  def reset(self):
58
63
  """Reset model"""
59
64
  self.delete_chat()
65
+ self._session_history_file = None
60
66
 
61
67
  @abstractmethod
62
68
  def chat(self, message: str) -> Generator[str, None, None]:
@@ -135,6 +141,7 @@ class BasePlatform(ABC):
135
141
  if first_chunk:
136
142
  break
137
143
  except StopIteration:
144
+ self._append_session_history(message, "")
138
145
  return ""
139
146
 
140
147
  text_content = Text(overflow="fold")
@@ -200,6 +207,7 @@ class BasePlatform(ABC):
200
207
  live.update(panel)
201
208
 
202
209
  if is_immediate_abort() and get_interrupt():
210
+ self._append_session_history(message, response)
203
211
  return response # Return the partial response immediately
204
212
 
205
213
  # Ensure any remaining content in the buffer is displayed
@@ -225,6 +233,7 @@ class BasePlatform(ABC):
225
233
  console.print(s, end="")
226
234
  response += s
227
235
  if is_immediate_abort() and get_interrupt():
236
+ self._append_session_history(message, response)
228
237
  return response
229
238
  console.print()
230
239
  end_time = time.time()
@@ -234,6 +243,7 @@ class BasePlatform(ABC):
234
243
  for s in self.chat(message):
235
244
  response += s
236
245
  if is_immediate_abort() and get_interrupt():
246
+ self._append_session_history(message, response)
237
247
  return response
238
248
  # Keep original think tag handling
239
249
  response = re.sub(
@@ -242,6 +252,8 @@ class BasePlatform(ABC):
242
252
  response = re.sub(
243
253
  ot("thinking") + r".*?" + ct("thinking"), "", response, flags=re.DOTALL
244
254
  )
255
+ # Save session history (input and full response)
256
+ self._append_session_history(message, response)
245
257
  return response
246
258
 
247
259
  def chat_until_success(self, message: str) -> str:
@@ -346,6 +358,51 @@ class BasePlatform(ABC):
346
358
  """Set web flag"""
347
359
  self.web = web
348
360
 
361
+ def _append_session_history(self, user_input: str, model_output: str) -> None:
362
+ """
363
+ Append the user input and model output to a session history file if enabled.
364
+ The file name is generated on first save and reused until reset.
365
+ """
366
+ try:
367
+ if not is_save_session_history():
368
+ return
369
+
370
+ if self._session_history_file is None:
371
+ # Ensure data directory exists
372
+ data_dir = get_data_dir()
373
+ os.makedirs(data_dir, exist_ok=True)
374
+
375
+ # Build a safe filename including platform, model and timestamp
376
+ try:
377
+ platform_name = type(self).platform_name()
378
+ except Exception:
379
+ platform_name = "unknown_platform"
380
+
381
+ try:
382
+ model_name = self.name()
383
+ except Exception:
384
+ model_name = "unknown_model"
385
+
386
+ safe_platform = re.sub(r"[^\w\-\.]+", "_", str(platform_name))
387
+ safe_model = re.sub(r"[^\w\-\.]+", "_", str(model_name))
388
+ ts = datetime.now().strftime("%Y%m%d_%H%M%S")
389
+
390
+ self._session_history_file = os.path.join(
391
+ data_dir, f"session_history_{safe_platform}_{safe_model}_{ts}.log"
392
+ )
393
+
394
+ # Append record
395
+ with open(self._session_history_file, "a", encoding="utf-8", errors="ignore") as f:
396
+ ts_line = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
397
+ f.write(f"===== {ts_line} =====\n")
398
+ f.write("USER:\n")
399
+ f.write(f"{user_input}\n")
400
+ f.write("\nASSISTANT:\n")
401
+ f.write(f"{model_output}\n\n")
402
+ except Exception:
403
+ # Do not break chat flow if writing history fails
404
+ pass
405
+
349
406
  @abstractmethod
350
407
  def support_web(self) -> bool:
351
408
  """Check if platform supports web functionality"""
@@ -24,7 +24,32 @@ class OpenAIModel(BasePlatform):
24
24
  self.base_url = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
25
25
  self.model_name = os.getenv("JARVIS_MODEL") or "gpt-4o"
26
26
 
27
- self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
27
+ # Optional: Inject extra HTTP headers via environment variable
28
+ # Expected format: OPENAI_EXTRA_HEADERS='{"Header-Name": "value", "X-Trace": "abc"}'
29
+ headers_str = os.getenv("OPENAI_EXTRA_HEADERS")
30
+ self.extra_headers: Dict[str, str] = {}
31
+ if headers_str:
32
+ try:
33
+ parsed = json.loads(headers_str)
34
+ if isinstance(parsed, dict):
35
+ # Ensure all header keys/values are strings
36
+ self.extra_headers = {str(k): str(v) for k, v in parsed.items()}
37
+ else:
38
+ PrettyOutput.print("OPENAI_EXTRA_HEADERS 应为 JSON 对象,如 {'X-Source':'jarvis'}", OutputType.WARNING)
39
+ except Exception as e:
40
+ PrettyOutput.print(f"解析 OPENAI_EXTRA_HEADERS 失败: {e}", OutputType.WARNING)
41
+
42
+ # Initialize OpenAI client, try to pass default headers if SDK supports it
43
+ try:
44
+ if self.extra_headers:
45
+ self.client = OpenAI(api_key=self.api_key, base_url=self.base_url, default_headers=self.extra_headers)
46
+ else:
47
+ self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
48
+ except TypeError:
49
+ # Fallback: SDK version may not support default_headers
50
+ self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
51
+ if self.extra_headers:
52
+ PrettyOutput.print("当前 OpenAI SDK 不支持 default_headers,未能注入额外 HTTP 头", OutputType.WARNING)
28
53
  self.messages: List[Dict[str, str]] = []
29
54
  self.system_message = ""
30
55
 
@@ -34,17 +34,18 @@ class FileSearchReplaceTool:
34
34
  - REPLACE: 替换后的新代码
35
35
  - 区间替换:
36
36
  - reason: 修改原因描述
37
- - SEARCH_START: 起始标记(包含在替换范围内)
38
- - SEARCH_END: 结束标记(包含在替换范围内)
37
+ - SEARCH_START: 起始标记
38
+ - SEARCH_END: 结束标记
39
39
  - REPLACE: 替换后的新代码
40
40
  - RANGE: 可选的行号范围 'start-end' (1-based, 闭区间), 用于限定匹配范围
41
+ - **说明**: 区间替换会从包含 SEARCH_START 的行首开始,到包含 SEARCH_END 的行尾结束,替换整个区域
41
42
 
42
43
  ## 核心原则
43
44
  1. **精准修改**: 只修改必要的代码部分,保持其他部分不变
44
45
  2. **最小补丁原则**: 生成最小范围的补丁,包含必要的上下文
45
46
  3. **唯一匹配**:
46
47
  - 单点替换:确保 SEARCH 在文件中唯一匹配
47
- - 区间替换:确保 SEARCH_START 在文件中唯一匹配,且在其后 SEARCH_END 也唯一匹配
48
+ - 区间替换:确保在指定范围内,第一个 SEARCH_START 之后能找到 SEARCH_END
48
49
  4. **格式保持**: 严格保持原始代码的格式风格
49
50
  5. **部分成功**: 支持多个文件编辑,允许部分文件编辑成功
50
51
 
@@ -121,11 +122,10 @@ class FileSearchReplaceTool:
121
122
  - REPLACE: 替换后的新代码
122
123
  2) 区间替换:
123
124
  - reason: 修改原因描述
124
- - SEARCH_START: 起始标记(包含在替换范围内)
125
- - SEARCH_END: 结束标记(包含在替换范围内)
125
+ - SEARCH_START: 起始标记
126
+ - SEARCH_END: 结束标记
126
127
  - REPLACE: 替换后的新代码
127
- 通用可选项:
128
- - RANGE: 形如 'start-end'(1-based,闭区间),仅用于区间替换模式。当提供时仅在该行号范围内执行匹配与替换;省略则在整个文件范围内处理
128
+ - **说明**: 区间替换会从包含 SEARCH_START 的行首开始,到包含 SEARCH_END 的行尾结束,替换整个区域
129
129
 
130
130
  返回:
131
131
  Dict[str, Any] 包含:
@@ -58,7 +58,8 @@ class WebpageTool:
58
58
  2. 包含网页标题
59
59
  3. 根据用户需求提供准确、完整的信息"""
60
60
  response = model.chat_until_success(prompt) # type: ignore
61
- return {"success": True, "stdout": response, "stderr": ""}
61
+ if response and response.strip():
62
+ return {"success": True, "stdout": response, "stderr": ""}
62
63
 
63
64
  # 2) 然后尝试使用默认平台(normal)的 web 能力
64
65
  model = PlatformRegistry().get_normal_platform()
@@ -73,7 +74,8 @@ class WebpageTool:
73
74
  2. 包含网页标题
74
75
  3. 根据用户需求提供准确、完整的信息"""
75
76
  response = model.chat_until_success(prompt) # type: ignore
76
- return {"success": True, "stdout": response, "stderr": ""}
77
+ if response and response.strip():
78
+ return {"success": True, "stdout": response, "stderr": ""}
77
79
 
78
80
  # 3) 回退:使用 requests 抓取网页,再用模型分析
79
81
 
@@ -147,11 +147,13 @@ class SearchWebTool:
147
147
  if model.support_web():
148
148
  model.set_web(True)
149
149
  model.set_suppress_output(False)
150
- return {
151
- "stdout": model.chat_until_success(query),
152
- "stderr": "",
153
- "success": True,
154
- }
150
+ response = model.chat_until_success(query)
151
+ if response and response.strip():
152
+ return {
153
+ "stdout": response,
154
+ "stderr": "",
155
+ "success": True,
156
+ }
155
157
 
156
158
  # 否则使用现有的模型选择流程
157
159
  if agent.model.support_web():
@@ -161,11 +163,13 @@ class SearchWebTool:
161
163
  model.set_model_name(agent.model.name())
162
164
  model.set_web(True)
163
165
  model.set_suppress_output(False)
164
- return {
165
- "stdout": model.chat_until_success(query),
166
- "stderr": "",
167
- "success": True,
168
- }
166
+ response = model.chat_until_success(query)
167
+ if response and response.strip():
168
+ return {
169
+ "stdout": response,
170
+ "stderr": "",
171
+ "success": True,
172
+ }
169
173
 
170
174
  return self._search_with_ddgs(query, agent)
171
175
 
@@ -730,6 +730,16 @@ def is_enable_builtin_config_selector() -> bool:
730
730
  )
731
731
 
732
732
 
733
+ def is_save_session_history() -> bool:
734
+ """
735
+ 是否保存会话记录。
736
+
737
+ 返回:
738
+ bool: 如果要保存会话记录则返回True, 默认为False
739
+ """
740
+ return GLOBAL_CONFIG_DATA.get("JARVIS_SAVE_SESSION_HISTORY", False) is True
741
+
742
+
733
743
  def is_immediate_abort() -> bool:
734
744
  """
735
745
  是否启用立即中断:当在对话过程中检测到用户中断信号时,立即停止输出并返回。
@@ -299,7 +299,7 @@ def get_modified_line_ranges() -> Dict[str, List[Tuple[int, int]]]:
299
299
  # 获取所有文件的Git差异
300
300
  # 仅用于解析修改行范围,减少上下文以降低输出体积和解析成本
301
301
  result = subprocess.run(
302
- ["git", "show", "-U0", "--no-color"],
302
+ ["git", "show", "--no-color"],
303
303
  capture_output=True,
304
304
  text=True,
305
305
  )
@@ -482,7 +482,7 @@ def _get_multiline_input_internal(
482
482
  """Handle Ctrl+O by exiting the prompt and returning the sentinel value."""
483
483
  event.app.exit(result=CTRL_O_SENTINEL)
484
484
 
485
- @bindings.add("c-t", filter=has_focus(DEFAULT_BUFFER))
485
+ @bindings.add("c-t", filter=has_focus(DEFAULT_BUFFER), eager=True)
486
486
  def _(event):
487
487
  """Return a shell command like '!bash' for upper input_handler to execute."""
488
488
 
@@ -1719,11 +1719,11 @@ def while_success(func: Callable[[], Any], sleep_time: float = 0.1, max_retries:
1719
1719
  try:
1720
1720
  result = func()
1721
1721
  break
1722
- except Exception:
1722
+ except Exception as e:
1723
1723
  retry_count += 1
1724
1724
  if retry_count < max_retries:
1725
1725
  PrettyOutput.print(
1726
- f"发生异常,重试中 ({retry_count}/{max_retries}),等待 {sleep_time}s...",
1726
+ f"发生异常:\n{e}\n重试中 ({retry_count}/{max_retries}),等待 {sleep_time}s...",
1727
1727
  OutputType.WARNING,
1728
1728
  )
1729
1729
  time.sleep(sleep_time)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.3.31
3
+ Version: 0.3.33
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,14 +1,15 @@
1
- jarvis/__init__.py,sha256=ty3_Sg4Xh7MccnSdYA1axmu51agflxzcbgAXl0e21-s,74
1
+ jarvis/__init__.py,sha256=pK5l6XHBkpMUJjvRS5nGlvLwpzdLLQEAyFtF-qstkkM,74
2
2
  jarvis/jarvis_agent/__init__.py,sha256=Mp0lcivL99cL2eIjHz-D1C86XMOhJkFYtnQcJoflfH4,49033
3
- jarvis/jarvis_agent/agent_manager.py,sha256=qNcMy5Xc5ZT26JfczBg4b4D5udKVHSFsCFjlpbIdmPo,3076
3
+ jarvis/jarvis_agent/agent_manager.py,sha256=UgMUz6fSV2XhQHhmIGHF53MyUytAPvZ1yC2f29LcTO8,3235
4
4
  jarvis/jarvis_agent/builtin_input_handler.py,sha256=wS-FqpT3pIXwHn1dfL3SpXonUKWgVThbQueUIeyRc2U,2917
5
5
  jarvis/jarvis_agent/config.py,sha256=Ni1aTVzmdERJ89A1jsC21Tsys_9MM-TTx1w5XwxyEwA,3130
6
6
  jarvis/jarvis_agent/config_editor.py,sha256=hlb9EYxKWcR_qdW2O89CgNDdciR9Isi743JU_1gD8j4,1927
7
- jarvis/jarvis_agent/edit_file_handler.py,sha256=liPsL133PvgiTywesqVulfJdhaw6PIq4HXVCFstjFJg,23786
7
+ jarvis/jarvis_agent/edit_file_handler.py,sha256=7xkEvlER6pNHtxyGad_ol23NeDGsYMunq4XmTAx86kw,24722
8
8
  jarvis/jarvis_agent/event_bus.py,sha256=pRdfk7d0OG18K6yNfWlCvAh_dW5p9sBtT2Yc3jGmzgo,1519
9
9
  jarvis/jarvis_agent/events.py,sha256=rmFQ37PasImCh7OCdCzNBvubk-kHwcUiYLgzmL0t0_4,3689
10
+ jarvis/jarvis_agent/file_context_handler.py,sha256=2MPn_O_2llX39meFg272Cjk3wMPn5nmgbGMUyX06YQo,2113
10
11
  jarvis/jarvis_agent/file_methodology_manager.py,sha256=LnhgTx5xQXCBK8esjCkFbgFm9iEyFX7TryUlC40Kzpw,4428
11
- jarvis/jarvis_agent/jarvis.py,sha256=IW3BlErR_B6UxreTdmD9tIMy0C3xb593LOtCUdFChQs,24368
12
+ jarvis/jarvis_agent/jarvis.py,sha256=CNuVLxbl9erQkEq41oWI7tFGi-AMiNBEJ56alQLm_m4,29111
12
13
  jarvis/jarvis_agent/main.py,sha256=bFcwXWC6O05jQiXy6ED_bHjdjDo5VwV_i1BoBEAzgP0,3336
13
14
  jarvis/jarvis_agent/memory_manager.py,sha256=WSyUffx9xTmkcj4QrSLEfsjI3sTMUwZmkkC9_N_gTjo,8042
14
15
  jarvis/jarvis_agent/methodology_share_manager.py,sha256=AB_J9BwRgaeENQfL6bH83FOLeLrgHhppMb7psJNevKs,6874
@@ -17,7 +18,7 @@ jarvis/jarvis_agent/prompt_builder.py,sha256=PH1fPDVa8z_RXkoXHJFNDf8PQjUoLNLYwkh
17
18
  jarvis/jarvis_agent/prompt_manager.py,sha256=_1qLBSA3yn4nT_N3X2npTpW40Cp-pMeyvnzu-pnG0iU,2720
18
19
  jarvis/jarvis_agent/prompts.py,sha256=CvbPYx_klEz6OQrxVReZAnC2uQNo53rWkkucmh30uKg,9531
19
20
  jarvis/jarvis_agent/protocols.py,sha256=YFJaC9MHi7JfLzmvlyotJDjiCO4Z07XJXy1gKhVdUy4,956
20
- jarvis/jarvis_agent/run_loop.py,sha256=GdOERKfQUTx5EtHMA-4ilmA__SJzXksheP44Oo6HF9c,4300
21
+ jarvis/jarvis_agent/run_loop.py,sha256=OWdJSq1dLC6xPx4EQBfSnD_rb3IwszwZp4KbXYiJtcg,4747
21
22
  jarvis/jarvis_agent/session_manager.py,sha256=5wVcaZGwJ9cEKTQglSbqyxUDJ2fI5KxYN8C8L16UWLw,3024
22
23
  jarvis/jarvis_agent/share_manager.py,sha256=MF2RlomcgPxF8nVUk28DP6IRddZ_tot5l_YRvy0qXSQ,8726
23
24
  jarvis/jarvis_agent/shell_input_handler.py,sha256=wiAPjB-9uTkcLszbO5dlOUwIfaeR39RgRcZhahIGqoA,2018
@@ -28,7 +29,7 @@ jarvis/jarvis_agent/tool_share_manager.py,sha256=Do08FRxis0ynwR2a6iRoa6Yq0qCP8Nk
28
29
  jarvis/jarvis_agent/user_interaction.py,sha256=tifFN49GkO_Q80sqOTVmhxwbNWTazF3K0cr8AnnvzdU,1453
29
30
  jarvis/jarvis_agent/utils.py,sha256=ldgfuNTNu4JU7Y1LtystBl85OC6H3A4OMycg0XBt_Cs,1615
30
31
  jarvis/jarvis_code_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- jarvis/jarvis_code_agent/code_agent.py,sha256=nCe0GP6mIzAjJBn2aFqVrDRSjMHnenkDrSoaM0VY87U,36576
32
+ jarvis/jarvis_code_agent/code_agent.py,sha256=lAnVTFxnnOsd2Vih9c-nSuczfz6CKTMhUQnhj8UcwWk,36735
32
33
  jarvis/jarvis_code_agent/lint.py,sha256=_qLJB_bC3PuoHG-j4EGOnYzNGO26jHlKLbkysfyQW1c,3954
33
34
  jarvis/jarvis_code_analysis/code_review.py,sha256=Z0JsvyVPPHPm6rfo4fqaQr7CdZKIllo9jqStzV0i_-o,34470
34
35
  jarvis/jarvis_code_analysis/checklists/__init__.py,sha256=LIXAYa1sW3l7foP6kohLWnE98I_EQ0T7z5bYKHq6rJA,78
@@ -51,7 +52,7 @@ jarvis/jarvis_code_analysis/checklists/shell.py,sha256=aRFYhQQvTgbYd-uY5pc8UHIUA
51
52
  jarvis/jarvis_code_analysis/checklists/sql.py,sha256=vR0T6qC7b4dURjJVAd7kSVxyvZEQXPG1Jqc2sNTGp5c,2355
52
53
  jarvis/jarvis_code_analysis/checklists/swift.py,sha256=TPx4I6Gupvs6tSerRKmTSKEPQpOLEbH2Y7LXg1uBgxc,2566
53
54
  jarvis/jarvis_code_analysis/checklists/web.py,sha256=25gGD7pDadZQybNFvALYxWvK0VRjGQb1NVJQElwjyk0,3943
54
- jarvis/jarvis_data/config_schema.json,sha256=q5kXr067yPYcebEWyhqFBmfX1KgyjTAr_hCku__zSZ4,14166
55
+ jarvis/jarvis_data/config_schema.json,sha256=K6Q4ltnULhIVqYTLUSIyiGcZiOaMwkpbhUXQ4U-kDqI,14307
55
56
  jarvis/jarvis_data/tiktoken/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
56
57
  jarvis/jarvis_git_squash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
58
  jarvis/jarvis_git_squash/main.py,sha256=BRbsEQVXwseVFKliVqV8_JPh1om6QT6dLTHw0jQ7OE0,2474
@@ -63,14 +64,14 @@ jarvis/jarvis_mcp/streamable_mcp_client.py,sha256=BenOeZGNHdUOJT5Z3cc5MhS6aOeKQg
63
64
  jarvis/jarvis_memory_organizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
65
  jarvis/jarvis_memory_organizer/memory_organizer.py,sha256=CMFL46vvtpcTI6oS3CAlYteR6xAlwCkvVJmMT22uDRw,26295
65
66
  jarvis/jarvis_methodology/main.py,sha256=uiNzk5b5O6xdvRhsOuD7ubxdd2tPcDsFFnvmes8uH8I,11370
66
- jarvis/jarvis_multi_agent/__init__.py,sha256=OD3ZyuxPNPHaqjQqiKiW0HuB0DI_sdv41wFlCISHWIQ,6084
67
+ jarvis/jarvis_multi_agent/__init__.py,sha256=fBWmW5S9gpEEYzCBJizyE2q0Jj2LUzj4yh7sfoe3Qrc,7170
67
68
  jarvis/jarvis_multi_agent/main.py,sha256=b9IThFMeUZCYSlgT-VT8r7xeBdrEE_zNT11awEc8IdY,1853
68
69
  jarvis/jarvis_platform/__init__.py,sha256=WLQHSiE87PPket2M50_hHzjdMIgPIBx2VF8JfB_NNRk,105
69
70
  jarvis/jarvis_platform/ai8.py,sha256=g8JkqPGs9SEbqstNMCc5rCHO0QcPHX9LNvb7HMWwB-Q,11471
70
- jarvis/jarvis_platform/base.py,sha256=u1XvfE83-S-3W_euMrMaaa8NdXeIHlo7VSxLBbN3K-Y,13504
71
+ jarvis/jarvis_platform/base.py,sha256=NhhiAiACNsL462zljtGsG61SD8X-RRJyOAz1n1ocAPw,15947
71
72
  jarvis/jarvis_platform/human.py,sha256=jWjW8prEag79e6ddqTPV4nz_Gz6zFBfO4a1EbvP8QWA,4908
72
73
  jarvis/jarvis_platform/kimi.py,sha256=KLsf9udAsPRMbQ2JkBeiAlXkupCBwdtMaJ-hpH4Jdkc,15711
73
- jarvis/jarvis_platform/openai.py,sha256=0YSeDGHRSPQP2haEzFARx_aZH_d_UZ-HSCsJLh2hW5k,8037
74
+ jarvis/jarvis_platform/openai.py,sha256=4YapmkmJmPGfrjktORcIejlB98b83Wsi_48zjBymHAc,9500
74
75
  jarvis/jarvis_platform/registry.py,sha256=YqaFM2LXcHvqMQrTEQ_cVo8V-wQ8HhOeSdC8DdjvC00,8045
75
76
  jarvis/jarvis_platform/tongyi.py,sha256=QQHDn-kNcb_UH-B49lVIQTlHzYd_O8CFWN7SZqy38S4,23456
76
77
  jarvis/jarvis_platform/yuanbao.py,sha256=jlttNWq-Bq9JLxaI6MOsxKZaNEXF1EfVVB7mjtVcYMA,23954
@@ -97,18 +98,18 @@ jarvis/jarvis_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
97
98
  jarvis/jarvis_tools/ask_user.py,sha256=M6DdLNryCE8y1JcdZHEifUgZkPUEPNKc-zDW5p0Mb1k,2029
98
99
  jarvis/jarvis_tools/base.py,sha256=tFZkRlbV_a-pbjM-ci9AYmXVJm__FXuzVWKbQEyz4Ao,1639
99
100
  jarvis/jarvis_tools/clear_memory.py,sha256=8DOq6dHLemfKTJqu227PWBIp8Iu5K7EXwINzL8DYk8M,8205
100
- jarvis/jarvis_tools/edit_file.py,sha256=HYUk31ehf2pKB7nY3nm_ahcqUv9bigaHSUc-f5e_0LQ,9129
101
+ jarvis/jarvis_tools/edit_file.py,sha256=UcJNTVCDEmRaQsN_7_Ip2LjrTDm4wWzj2deuZHiBvLI,9026
101
102
  jarvis/jarvis_tools/execute_script.py,sha256=oDOMn8GcV6qKP4d0RFT6xbHGTazRmaOlp-h_e_Wj80c,6227
102
103
  jarvis/jarvis_tools/file_analyzer.py,sha256=jzVb8fAJn3dWwpCiYH-Wuxva4kpHqBB2_V3x3mzY0Gs,4158
103
104
  jarvis/jarvis_tools/generate_new_tool.py,sha256=tJz0YtfDwyH9y00VEWw3Btqr9JCNhvtI8BN9i5hYk_M,8560
104
105
  jarvis/jarvis_tools/methodology.py,sha256=_K4GIDUodGEma3SvNRo7Qs5rliijgNespVLyAPN35JU,5233
105
106
  jarvis/jarvis_tools/read_code.py,sha256=F1RuO0c69t0h7CvrUGqrTyNcOCcUrFQPACc61O_YSso,6382
106
- jarvis/jarvis_tools/read_webpage.py,sha256=YTmoalY8y-jdQuoj9IL6ZjXPOevUj2P_9arJngPhbUY,5317
107
+ jarvis/jarvis_tools/read_webpage.py,sha256=dfyXJ9vaX-ZRbua1P5ZlaU_SlSzKkeNw-1kI_3-gxFE,5433
107
108
  jarvis/jarvis_tools/registry.py,sha256=yVXBrJ7plyn7Dr3dD6mPmgd6eiBftmd19Cc84-PwVN8,33312
108
109
  jarvis/jarvis_tools/retrieve_memory.py,sha256=hhhGSr7jebPHICY9oEKICyI8mfqsRtKjh58qZNZApKc,8624
109
110
  jarvis/jarvis_tools/rewrite_file.py,sha256=CuvjWPTbUaPbex9FKSmw_Ru4r6R-CX_3vqTqCTp8nHA,6959
110
111
  jarvis/jarvis_tools/save_memory.py,sha256=RQtNxcpU53FFv_EBjH0i0oyQ7jWubm-trD1BHuqaGjI,6985
111
- jarvis/jarvis_tools/search_web.py,sha256=nkbmyIquGLl2JkgWP6pQ9dPcLlfQCuegwt_RKE0YWU0,6158
112
+ jarvis/jarvis_tools/search_web.py,sha256=Hi8WBxcRH02qjOF1qcJP2qSqs3kVOKGFAARfh548Ii4,6370
112
113
  jarvis/jarvis_tools/sub_agent.py,sha256=kjMZBXQE3OUgm5eO9lNkOuBnugWQGZbCpVP0HNW5W2s,8905
113
114
  jarvis/jarvis_tools/sub_code_agent.py,sha256=vVPcGKfgyhbZzl8vp2HHbgR1oQzC0TlS0G3THoZgU5Q,9453
114
115
  jarvis/jarvis_tools/virtual_tty.py,sha256=L7-J00ARQvIa25T45Hhqg2eCBl6W2LFgqDlWMWf-7dk,25275
@@ -117,21 +118,21 @@ jarvis/jarvis_tools/cli/main.py,sha256=WL2GNV7WqYl7G1-btRGvCkzDCMk4fPfNvzCrnUFVP
117
118
  jarvis/jarvis_utils/__init__.py,sha256=67h0ldisGlh3oK4DAeNEL2Bl_VsI3tSmfclasyVlueM,850
118
119
  jarvis/jarvis_utils/builtin_replace_map.py,sha256=z8iAqsbZUiGFaozxG1xSu128op8udqHOeEw-GxNt4bU,1708
119
120
  jarvis/jarvis_utils/clipboard.py,sha256=D3wzQeqg_yiH7Axs4d6MRxyNa9XxdnenH-ND2uj2WVQ,2967
120
- jarvis/jarvis_utils/config.py,sha256=AbDfL6hBpD6G_cRlr_bOAXECsj_vIq9tHXgkbsBIv5E,21119
121
+ jarvis/jarvis_utils/config.py,sha256=e5S6-H0Up3OupUu0ATESh3UWkjEfsbkyxDf_0fPPBpU,21372
121
122
  jarvis/jarvis_utils/embedding.py,sha256=x6mrkL7Bc3qgfuBDsjc4fg4nKG8ofGxVLVVydbsb8PY,2838
122
123
  jarvis/jarvis_utils/file_processors.py,sha256=XiM248SHS7lLgQDCbORVFWqinbVDUawYxWDOsLXDxP8,3043
123
124
  jarvis/jarvis_utils/fzf.py,sha256=vCs0Uh5dUqGbWzXn2JCtLLCOYE2B39ZNdNveR9PK4DA,1681
124
- jarvis/jarvis_utils/git_utils.py,sha256=Wz0oTOsqdQ0JWl69pj02PTI0jrdC6VOizd1uw0QXw_U,24078
125
+ jarvis/jarvis_utils/git_utils.py,sha256=zxjdxbFb_X6aYo-w1fbMx3d2n1ScbmmaAYlE3wGaaSg,24071
125
126
  jarvis/jarvis_utils/globals.py,sha256=7Xvf9HY6jYJL4vSD1F1WCoxHkHCAyltJUYt4V9gGVU4,8865
126
127
  jarvis/jarvis_utils/http.py,sha256=eRhV3-GYuWmQ0ogq9di9WMlQkFcVb1zGCrySnOgT1x0,4392
127
- jarvis/jarvis_utils/input.py,sha256=EBdjPopkxVpG4JsnP9gtTSJ10u_scagujyKyOMhatLQ,36524
128
+ jarvis/jarvis_utils/input.py,sha256=BT-aAaOBFsb4YeqR5ceIsa-j_MYEmsAvQqrXQSknA7c,36536
128
129
  jarvis/jarvis_utils/methodology.py,sha256=z_renvRGgHiC-XTQPuN6rvrJ_ffHlwxK_b1BU_jmNAQ,12800
129
130
  jarvis/jarvis_utils/output.py,sha256=y2fVcao_2ZowFl0IxUrJZCi8T6ZM0z-iPzpk8T8eLxc,13623
130
131
  jarvis/jarvis_utils/tag.py,sha256=f211opbbbTcSyzCDwuIK_oCnKhXPNK-RknYyGzY1yD0,431
131
- jarvis/jarvis_utils/utils.py,sha256=1rfnpFXeCp-6dL9_4eNOXnFvxZduTCoMWBdfDx7AMrE,72718
132
- jarvis_ai_assistant-0.3.31.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
133
- jarvis_ai_assistant-0.3.31.dist-info/METADATA,sha256=JdHbYQDNFNAhhuk5lxOvFXNRvVRDx8y7nA6_pzMSVbM,18752
134
- jarvis_ai_assistant-0.3.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
135
- jarvis_ai_assistant-0.3.31.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
136
- jarvis_ai_assistant-0.3.31.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
137
- jarvis_ai_assistant-0.3.31.dist-info/RECORD,,
132
+ jarvis/jarvis_utils/utils.py,sha256=uMtfaStxDtp2i9AFIxwtPKgSxLwQxw8Z2rXsX-ZGlis,72728
133
+ jarvis_ai_assistant-0.3.33.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
134
+ jarvis_ai_assistant-0.3.33.dist-info/METADATA,sha256=GnrDTF9-OWDXNJUjGxlbBxUFUXR5ZmWCopRNw6YiVsk,18752
135
+ jarvis_ai_assistant-0.3.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
136
+ jarvis_ai_assistant-0.3.33.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
137
+ jarvis_ai_assistant-0.3.33.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
138
+ jarvis_ai_assistant-0.3.33.dist-info/RECORD,,