jarvis-ai-assistant 0.3.26__py3-none-any.whl → 0.3.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +303 -177
  3. jarvis/jarvis_agent/agent_manager.py +6 -0
  4. jarvis/jarvis_agent/config.py +92 -0
  5. jarvis/jarvis_agent/config_editor.py +1 -1
  6. jarvis/jarvis_agent/event_bus.py +48 -0
  7. jarvis/jarvis_agent/file_methodology_manager.py +1 -3
  8. jarvis/jarvis_agent/jarvis.py +77 -36
  9. jarvis/jarvis_agent/memory_manager.py +70 -3
  10. jarvis/jarvis_agent/prompt_manager.py +82 -0
  11. jarvis/jarvis_agent/run_loop.py +130 -0
  12. jarvis/jarvis_agent/shell_input_handler.py +1 -1
  13. jarvis/jarvis_agent/task_analyzer.py +89 -11
  14. jarvis/jarvis_agent/task_manager.py +26 -0
  15. jarvis/jarvis_agent/user_interaction.py +42 -0
  16. jarvis/jarvis_code_agent/code_agent.py +18 -3
  17. jarvis/jarvis_code_agent/lint.py +5 -5
  18. jarvis/jarvis_code_analysis/code_review.py +0 -1
  19. jarvis/jarvis_data/config_schema.json +7 -6
  20. jarvis/jarvis_git_squash/main.py +6 -1
  21. jarvis/jarvis_git_utils/git_commiter.py +51 -16
  22. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  23. jarvis/jarvis_memory_organizer/memory_organizer.py +2 -5
  24. jarvis/jarvis_methodology/main.py +0 -2
  25. jarvis/jarvis_multi_agent/__init__.py +3 -3
  26. jarvis/jarvis_platform/base.py +5 -6
  27. jarvis/jarvis_platform/registry.py +1 -1
  28. jarvis/jarvis_platform/yuanbao.py +0 -1
  29. jarvis/jarvis_platform_manager/main.py +28 -11
  30. jarvis/jarvis_platform_manager/service.py +1 -1
  31. jarvis/jarvis_rag/cli.py +1 -1
  32. jarvis/jarvis_rag/embedding_manager.py +0 -1
  33. jarvis/jarvis_rag/llm_interface.py +0 -3
  34. jarvis/jarvis_smart_shell/main.py +0 -1
  35. jarvis/jarvis_stats/cli.py +15 -35
  36. jarvis/jarvis_stats/stats.py +178 -51
  37. jarvis/jarvis_tools/clear_memory.py +1 -3
  38. jarvis/jarvis_tools/cli/main.py +0 -1
  39. jarvis/jarvis_tools/edit_file.py +0 -1
  40. jarvis/jarvis_tools/generate_new_tool.py +3 -5
  41. jarvis/jarvis_tools/registry.py +17 -3
  42. jarvis/jarvis_tools/retrieve_memory.py +2 -3
  43. jarvis/jarvis_tools/save_memory.py +3 -3
  44. jarvis/jarvis_tools/search_web.py +2 -2
  45. jarvis/jarvis_tools/sub_agent.py +114 -85
  46. jarvis/jarvis_tools/sub_code_agent.py +29 -7
  47. jarvis/jarvis_tools/virtual_tty.py +3 -14
  48. jarvis/jarvis_utils/builtin_replace_map.py +4 -4
  49. jarvis/jarvis_utils/config.py +44 -15
  50. jarvis/jarvis_utils/fzf.py +56 -0
  51. jarvis/jarvis_utils/git_utils.py +1 -1
  52. jarvis/jarvis_utils/globals.py +1 -2
  53. jarvis/jarvis_utils/input.py +0 -3
  54. jarvis/jarvis_utils/methodology.py +3 -5
  55. jarvis/jarvis_utils/output.py +1 -1
  56. jarvis/jarvis_utils/utils.py +117 -27
  57. {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/METADATA +2 -3
  58. {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/RECORD +62 -56
  59. {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/WHEEL +0 -0
  60. {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/entry_points.txt +0 -0
  61. {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/licenses/LICENSE +0 -0
  62. {jarvis_ai_assistant-0.3.26.dist-info → jarvis_ai_assistant-0.3.28.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ from jarvis.jarvis_utils.input import get_multiline_input, get_single_line_input
18
18
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
19
19
  from jarvis.jarvis_utils.utils import init_env
20
20
  from jarvis.jarvis_platform_manager.service import start_service
21
+ from jarvis.jarvis_utils.fzf import fzf_select
21
22
 
22
23
  app = typer.Typer(help="Jarvis AI 平台")
23
24
 
@@ -451,17 +452,33 @@ def role_command(
451
452
  )
452
453
  PrettyOutput.print(output_str, OutputType.INFO)
453
454
 
454
- # 让用户选择角色
455
- raw_choice = get_single_line_input("请选择角色(输入编号,直接回车退出): ")
456
- if not raw_choice.strip():
457
- PrettyOutput.print("已取消,退出程序", OutputType.INFO)
458
- raise typer.Exit(code=0)
459
- try:
460
- choice = int(raw_choice)
461
- selected_role = config["roles"][choice - 1]
462
- except (ValueError, IndexError):
463
- PrettyOutput.print("无效的选择", OutputType.ERROR)
464
- return
455
+ # 让用户选择角色(优先 fzf,回退编号输入)
456
+ selected_role = None # type: ignore[var-annotated]
457
+ fzf_options = [
458
+ f"{i:>3} | {role['name']} - {role.get('description', '')}"
459
+ for i, role in enumerate(config["roles"], 1)
460
+ ]
461
+ selected_str = fzf_select(fzf_options, prompt="选择角色编号 (Enter退出) > ")
462
+ if selected_str:
463
+ try:
464
+ num_part = selected_str.split("|", 1)[0].strip()
465
+ idx = int(num_part)
466
+ if 1 <= idx <= len(config["roles"]):
467
+ selected_role = config["roles"][idx - 1]
468
+ except Exception:
469
+ selected_role = None
470
+
471
+ if selected_role is None:
472
+ raw_choice = get_single_line_input("请选择角色(输入编号,直接回车退出): ")
473
+ if not raw_choice.strip():
474
+ PrettyOutput.print("已取消,退出程序", OutputType.INFO)
475
+ raise typer.Exit(code=0)
476
+ try:
477
+ choice = int(raw_choice)
478
+ selected_role = config["roles"][choice - 1]
479
+ except (ValueError, IndexError):
480
+ PrettyOutput.print("无效的选择", OutputType.ERROR)
481
+ return
465
482
 
466
483
 
467
484
 
@@ -10,7 +10,7 @@ import time
10
10
  import threading
11
11
  import uuid
12
12
  from datetime import datetime
13
- from typing import Any, Dict, List, Optional, Union
13
+ from typing import Any, Dict, List, Optional
14
14
 
15
15
  import uvicorn
16
16
  from fastapi import FastAPI, HTTPException
jarvis/jarvis_rag/cli.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import sys
3
3
  from pathlib import Path
4
- from typing import Optional, List, Literal, cast, Tuple
4
+ from typing import Optional, List, Tuple
5
5
  import mimetypes
6
6
 
7
7
  import pathspec # type: ignore
@@ -43,7 +43,6 @@ class EmbeddingManager:
43
43
  try:
44
44
  # First try to load model from local cache without any network access
45
45
  try:
46
- from sentence_transformers import SentenceTransformer
47
46
  local_dir = None
48
47
  # Prefer explicit local dir via env or direct path
49
48
 
@@ -1,7 +1,4 @@
1
1
  from abc import ABC, abstractmethod
2
- import os
3
- import os
4
- from abc import ABC, abstractmethod
5
2
 
6
3
  from jarvis.jarvis_agent import Agent as JarvisAgent
7
4
  from jarvis.jarvis_platform.base import BasePlatform
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  import os
4
- import sys
5
4
  from typing import Optional, Tuple
6
5
 
7
6
  import typer
@@ -16,7 +16,6 @@ from pathlib import Path
16
16
  from .stats import StatsManager
17
17
  from jarvis.jarvis_utils.utils import init_env
18
18
  from jarvis.jarvis_utils.config import get_data_dir
19
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
20
19
 
21
20
  app = typer.Typer(help="Jarvis 统计模块命令行工具")
22
21
  console = Console()
@@ -98,11 +97,6 @@ def inc(
98
97
  @app.command()
99
98
  def show(
100
99
  metric: Optional[str] = typer.Argument(None, help="指标名称,不指定则显示所有"),
101
- last_hours: Optional[int] = typer.Option(None, "--hours", "-h", help="最近N小时"),
102
- last_days: Optional[int] = typer.Option(None, "--days", "-d", help="最近N天"),
103
- format: str = typer.Option(
104
- "table", "--format", "-f", help="显示格式: table/chart/summary"
105
- ),
106
100
  aggregation: str = typer.Option(
107
101
  "hourly", "--agg", "-a", help="聚合方式: hourly/daily"
108
102
  ),
@@ -123,9 +117,7 @@ def show(
123
117
 
124
118
  stats.show(
125
119
  metric_name=metric,
126
- last_hours=last_hours,
127
- last_days=last_days,
128
- format=format,
120
+
129
121
  aggregation=aggregation,
130
122
  tags=tag_dict if tag_dict else None,
131
123
  )
@@ -136,8 +128,6 @@ def plot(
136
128
  metric: Optional[str] = typer.Argument(
137
129
  None, help="指标名称(可选,不指定则根据标签过滤所有匹配的指标)"
138
130
  ),
139
- last_hours: Optional[int] = typer.Option(None, "--hours", "-h", help="最近N小时"),
140
- last_days: Optional[int] = typer.Option(None, "--days", "-d", help="最近N天"),
141
131
  aggregation: str = typer.Option(
142
132
  "hourly", "--agg", "-a", help="聚合方式: hourly/daily"
143
133
  ),
@@ -160,8 +150,6 @@ def plot(
160
150
 
161
151
  stats.plot(
162
152
  metric_name=metric,
163
- last_hours=last_hours,
164
- last_days=last_days,
165
153
  aggregation=aggregation,
166
154
  width=width,
167
155
  height=height,
@@ -260,9 +248,9 @@ def clean(
260
248
  @app.command()
261
249
  def export(
262
250
  metric: str = typer.Argument(..., help="指标名称"),
263
- output: str = typer.Option("csv", "--format", "-f", help="输出格式: csv/json"),
264
- last_hours: Optional[int] = typer.Option(None, "--hours", "-h", help="最近N小时"),
265
- last_days: Optional[int] = typer.Option(None, "--days", "-d", help="最近N天"),
251
+
252
+
253
+
266
254
  tags: Optional[List[str]] = typer.Option(
267
255
  None, "--tag", "-t", help="标签过滤,格式: key=value"
268
256
  ),
@@ -285,27 +273,19 @@ def export(
285
273
  # 获取数据
286
274
  data = stats.get_stats(
287
275
  metric_name=metric,
288
- last_hours=last_hours,
289
- last_days=last_days,
290
276
  tags=tag_dict if tag_dict else None,
291
277
  )
292
278
 
293
- if output == "json":
294
- # JSON格式输出
295
- PrettyOutput.print(
296
- json.dumps(data, indent=2, ensure_ascii=False), OutputType.CODE, lang="json"
297
- )
279
+ # CSV格式输出(默认)
280
+ records = data.get("records", [])
281
+ if records:
282
+ writer = csv.writer(sys.stdout)
283
+ writer.writerow(["timestamp", "value", "tags"])
284
+ for record in records:
285
+ tags_str = json.dumps(record.get("tags", {}))
286
+ writer.writerow([record["timestamp"], record["value"], tags_str])
298
287
  else:
299
- # CSV格式输出
300
- records = data.get("records", [])
301
- if records:
302
- writer = csv.writer(sys.stdout)
303
- writer.writerow(["timestamp", "value", "tags"])
304
- for record in records:
305
- tags_str = json.dumps(record.get("tags", {}))
306
- writer.writerow([record["timestamp"], record["value"], tags_str])
307
- else:
308
- rprint("[yellow]没有找到数据[/yellow]", file=sys.stderr)
288
+ rprint("[yellow]没有找到数据[/yellow]", file=sys.stderr)
309
289
 
310
290
 
311
291
  @app.command()
@@ -329,7 +309,7 @@ def remove(
329
309
  unit = info.get("unit", "-")
330
310
  last_updated = info.get("last_updated", "-")
331
311
 
332
- rprint(f"\n[yellow]准备删除指标:[/yellow]")
312
+ rprint("\n[yellow]准备删除指标:[/yellow]")
333
313
  rprint(f" 名称: {metric}")
334
314
  rprint(f" 单位: {unit}")
335
315
  rprint(f" 最后更新: {last_updated}")
@@ -359,7 +339,7 @@ def demo():
359
339
  stats = StatsManager(_get_stats_dir())
360
340
 
361
341
  # 添加演示数据
362
- with console.status("[bold green]正在生成演示数据...") as status:
342
+ with console.status("[bold green]正在生成演示数据..."):
363
343
  # API响应时间
364
344
  for i in range(20):
365
345
  response_time = random.uniform(0.1, 2.0)
@@ -166,32 +166,57 @@ class StatsManager:
166
166
  >>> StatsManager.show("response_time", last_days=7, format="chart") # 图表显示
167
167
  """
168
168
  # 处理时间范围
169
- if end_time is None:
170
- end_time = datetime.now()
171
-
172
- if start_time is None:
173
- if last_hours:
174
- start_time = end_time - timedelta(hours=last_hours)
175
- elif last_days:
176
- start_time = end_time - timedelta(days=last_days)
177
- else:
178
- start_time = end_time - timedelta(days=7) # 默认7天
169
+ # 当未提供时间过滤参数时,默认显示全历史数据
170
+ if last_hours or last_days:
171
+ if end_time is None:
172
+ end_time = datetime.now()
173
+ if start_time is None:
174
+ if last_hours:
175
+ start_time = end_time - timedelta(hours=last_hours)
176
+ elif last_days:
177
+ start_time = end_time - timedelta(days=last_days)
178
+ else:
179
+ pass
179
180
 
180
181
  if metric_name is None:
181
182
  # 显示所有指标摘要
182
183
  StatsManager._show_metrics_summary(start_time, end_time, tags)
183
184
  else:
185
+ # 基于元数据的 created_at / last_updated 推断全历史时间范围
186
+ storage = StatsManager._get_storage()
187
+ if start_time is None or end_time is None:
188
+ info = storage.get_metric_info(metric_name)
189
+ if info:
190
+ try:
191
+ created_at = info.get("created_at")
192
+ last_updated = info.get("last_updated")
193
+ start_dt = start_time or (
194
+ datetime.fromisoformat(created_at) if created_at else datetime.now()
195
+ )
196
+ end_dt = end_time or (
197
+ datetime.fromisoformat(last_updated) if last_updated else datetime.now()
198
+ )
199
+ except Exception:
200
+ start_dt = start_time or (datetime.now() - timedelta(days=7))
201
+ end_dt = end_time or datetime.now()
202
+ else:
203
+ start_dt = start_time or (datetime.now() - timedelta(days=7))
204
+ end_dt = end_time or datetime.now()
205
+ else:
206
+ start_dt = start_time
207
+ end_dt = end_time
208
+
184
209
  # 根据格式显示数据
185
210
  if format == "chart":
186
211
  StatsManager._show_chart(
187
- metric_name, start_time, end_time, aggregation, tags
212
+ metric_name, start_dt, end_dt, aggregation, tags
188
213
  )
189
214
  elif format == "summary":
190
215
  StatsManager._show_summary(
191
- metric_name, start_time, end_time, aggregation, tags
216
+ metric_name, start_dt, end_dt, aggregation, tags
192
217
  )
193
218
  else:
194
- StatsManager._show_table(metric_name, start_time, end_time, tags)
219
+ StatsManager._show_table(metric_name, start_dt, end_dt, tags)
195
220
 
196
221
  @staticmethod
197
222
  def plot(
@@ -224,21 +249,44 @@ class StatsManager:
224
249
  >>> StatsManager.plot(tags={"service": "api"}, last_days=7)
225
250
  """
226
251
  # 处理时间范围
227
- if end_time is None:
228
- end_time = datetime.now()
229
-
230
- if start_time is None:
231
- if last_hours:
232
- start_time = end_time - timedelta(hours=last_hours)
233
- elif last_days:
234
- start_time = end_time - timedelta(days=last_days)
235
- else:
236
- start_time = end_time - timedelta(days=7)
252
+ # 当未提供时间过滤参数时,默认使用全历史数据
253
+ if last_hours or last_days:
254
+ if end_time is None:
255
+ end_time = datetime.now()
256
+ if start_time is None:
257
+ if last_hours:
258
+ start_time = end_time - timedelta(hours=last_hours)
259
+ elif last_days:
260
+ start_time = end_time - timedelta(days=last_days)
237
261
 
238
262
  # 如果指定了metric_name,显示单个图表
239
263
  if metric_name:
264
+ # 基于元数据的 created_at / last_updated 推断全历史时间范围
265
+ storage = StatsManager._get_storage()
266
+ if start_time is None or end_time is None:
267
+ info = storage.get_metric_info(metric_name)
268
+ if info:
269
+ try:
270
+ created_at = info.get("created_at")
271
+ last_updated = info.get("last_updated")
272
+ start_dt = start_time or (
273
+ datetime.fromisoformat(created_at) if created_at else datetime.now()
274
+ )
275
+ end_dt = end_time or (
276
+ datetime.fromisoformat(last_updated) if last_updated else datetime.now()
277
+ )
278
+ except Exception:
279
+ start_dt = start_time or (datetime.now() - timedelta(days=7))
280
+ end_dt = end_time or datetime.now()
281
+ else:
282
+ start_dt = start_time or (datetime.now() - timedelta(days=7))
283
+ end_dt = end_time or datetime.now()
284
+ else:
285
+ start_dt = start_time
286
+ end_dt = end_time
287
+
240
288
  StatsManager._show_chart(
241
- metric_name, start_time, end_time, aggregation, tags, width, height
289
+ metric_name, start_dt, end_dt, aggregation, tags, width, height
242
290
  )
243
291
  else:
244
292
  # 如果没有指定metric_name,根据标签过滤获取所有匹配的指标
@@ -272,32 +320,53 @@ class StatsManager:
272
320
  统计数据字典
273
321
  """
274
322
  # 处理时间范围
275
- if end_time is None:
276
- end_time = datetime.now()
277
-
278
- if start_time is None:
279
- if last_hours:
280
- start_time = end_time - timedelta(hours=last_hours)
281
- elif last_days:
282
- start_time = end_time - timedelta(days=last_days)
283
- else:
284
- start_time = end_time - timedelta(days=7)
323
+ # 当未提供时间过滤参数时,返回全历史数据
324
+ if last_hours or last_days or start_time is not None:
325
+ if end_time is None:
326
+ end_time = datetime.now()
327
+ if start_time is None:
328
+ if last_hours:
329
+ start_time = end_time - timedelta(hours=last_hours)
330
+ elif last_days:
331
+ start_time = end_time - timedelta(days=last_days)
285
332
 
286
333
  storage = StatsManager._get_storage()
334
+
335
+ # 基于元数据推断全历史范围(当未提供时间过滤参数时)
336
+ start_dt = start_time
337
+ end_dt = end_time
338
+ if start_dt is None or end_dt is None:
339
+ info = storage.get_metric_info(metric_name)
340
+ if info:
341
+ try:
342
+ created_at = info.get("created_at")
343
+ last_updated = info.get("last_updated")
344
+ if start_dt is None and created_at:
345
+ start_dt = datetime.fromisoformat(created_at)
346
+ if end_dt is None and last_updated:
347
+ end_dt = datetime.fromisoformat(last_updated)
348
+ except Exception:
349
+ pass
350
+ if start_dt is None:
351
+ start_dt = datetime.now() - timedelta(days=7)
352
+ if end_dt is None:
353
+ end_dt = datetime.now()
354
+
287
355
  if aggregation:
288
- # 返回聚合数据
356
+ # 返回聚合数据(按推断的全历史时间范围)
289
357
  return storage.aggregate_metrics(
290
- metric_name, start_time, end_time, aggregation, tags
358
+ metric_name, start_dt, end_dt, aggregation, tags
291
359
  )
292
360
  else:
293
- # 返回原始数据
294
- records = storage.get_metrics(metric_name, start_time, end_time, tags)
361
+ # 返回原始数据(全历史或指定范围)
362
+ records = storage.get_metrics(metric_name, start_dt, end_dt, tags)
363
+
295
364
  return {
296
365
  "metric": metric_name,
297
366
  "records": records,
298
367
  "count": len(records),
299
- "start_time": start_time.isoformat(),
300
- "end_time": end_time.isoformat(),
368
+ "start_time": start_dt.isoformat(),
369
+ "end_time": end_dt.isoformat(),
301
370
  }
302
371
 
303
372
  @staticmethod
@@ -343,24 +412,56 @@ class StatsManager:
343
412
  console.print("[yellow]没有找到任何统计指标[/yellow]")
344
413
  return
345
414
 
346
- # 如果没有指定时间范围,使用默认值
347
- if end_time is None:
348
- end_time = datetime.now()
349
- if start_time is None:
350
- start_time = end_time - timedelta(days=7)
415
+ # 如果未指定时间范围且两者皆为 None,则表示使用全历史数据
416
+ all_time = False
417
+ if start_time is None and end_time is None:
418
+ all_time = True
419
+ else:
420
+ if end_time is None:
421
+ end_time = datetime.now()
422
+ if start_time is None:
423
+ start_time = end_time - timedelta(days=7)
424
+
425
+
426
+
427
+ # 计算时间范围列标题
428
+ if start_time is None or end_time is None:
429
+ period_label = "值"
430
+ else:
431
+ delta = end_time - start_time
432
+ if delta.days >= 1:
433
+ period_label = f"{delta.days}天数据点"
434
+ else:
435
+ hours = max(1, int(delta.total_seconds() // 3600))
436
+ period_label = f"{hours}小时数据点"
351
437
 
352
438
  # 创建表格
353
439
  table = Table(title="统计指标摘要")
354
440
  table.add_column("指标名称", style="cyan")
355
441
  table.add_column("单位", style="green")
356
442
  table.add_column("最后更新", style="yellow")
357
- table.add_column("7天数据点", style="magenta")
443
+ table.add_column(period_label, style="magenta")
358
444
 
359
445
  # 过滤满足标签条件的指标
360
446
  displayed_count = 0
361
447
  for metric in metrics:
362
- # 获取该指标的记录
363
- records = storage.get_metrics(metric, start_time, end_time, tags)
448
+ # 获取该指标的记录(全历史或指定范围)
449
+ if 'all_time' in locals() and all_time:
450
+ _start = None
451
+ _end = None
452
+ try:
453
+ info = storage.get_metric_info(metric)
454
+ if info:
455
+ ca = info.get("created_at")
456
+ lu = info.get("last_updated")
457
+ _start = datetime.fromisoformat(ca) if ca else None
458
+ _end = datetime.fromisoformat(lu) if lu else None
459
+ except Exception:
460
+ _start = None
461
+ _end = None
462
+ records = storage.get_metrics(metric, _start, _end, tags)
463
+ else:
464
+ records = storage.get_metrics(metric, start_time, end_time, tags)
364
465
 
365
466
  # 如果指定了标签过滤,但没有匹配的记录,跳过该指标
366
467
  if tags and len(records) == 0:
@@ -382,8 +483,14 @@ class StatsManager:
382
483
  except:
383
484
  pass
384
485
 
385
- count = len(records)
386
- table.add_row(metric, unit, last_updated, str(count))
486
+ total_value = sum(r.get("value", 0) for r in records)
487
+ # 智能格式化,如果是整数则不显示小数点
488
+ if total_value == int(total_value):
489
+ value_to_display = str(int(total_value))
490
+ else:
491
+ value_to_display = f"{total_value:.2f}"
492
+
493
+ table.add_row(metric, unit, last_updated, value_to_display)
387
494
  displayed_count += 1
388
495
 
389
496
  if displayed_count == 0:
@@ -532,8 +639,28 @@ class StatsManager:
532
639
  if i > 0:
533
640
  console.print("\n" + "=" * 80 + "\n") # 分隔符
534
641
 
642
+ # 计算该指标的有效时间范围(基于元数据推断全历史)
643
+ _start = start_time
644
+ _end = end_time
645
+ if _start is None or _end is None:
646
+ info = storage.get_metric_info(metric)
647
+ if info:
648
+ try:
649
+ ca = info.get("created_at")
650
+ lu = info.get("last_updated")
651
+ if _start is None and ca:
652
+ _start = datetime.fromisoformat(ca)
653
+ if _end is None and lu:
654
+ _end = datetime.fromisoformat(lu)
655
+ except Exception:
656
+ pass
657
+ if _start is None:
658
+ _start = datetime.now() - timedelta(days=7)
659
+ if _end is None:
660
+ _end = datetime.now()
661
+
535
662
  StatsManager._show_chart(
536
- metric, start_time, end_time, aggregation, tags, width, height
663
+ metric, _start, _end, aggregation, tags, width, height
537
664
  )
538
665
 
539
666
  @staticmethod
@@ -1,6 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import json
3
- import shutil
4
3
  from pathlib import Path
5
4
  from typing import Any, Dict, List, Optional
6
5
 
@@ -8,7 +7,6 @@ from jarvis.jarvis_utils.config import get_data_dir
8
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
8
  from jarvis.jarvis_utils.globals import (
10
9
  clear_short_term_memories,
11
- get_short_term_memories,
12
10
  short_term_memories,
13
11
  )
14
12
 
@@ -212,7 +210,7 @@ class ClearMemoryTool:
212
210
  # 生成结果报告
213
211
 
214
212
  # 详细报告
215
- report = f"# 记忆清除报告\n\n"
213
+ report = "# 记忆清除报告\n\n"
216
214
  report += f"**总计清除**: {total_removed} 条记忆\n\n"
217
215
 
218
216
  if tags:
@@ -1,4 +1,3 @@
1
- import sys
2
1
  import json
3
2
  from typing import Optional
4
3
 
@@ -123,7 +123,6 @@ class FileSearchReplaceTool:
123
123
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
124
124
 
125
125
  stdout_messages: list[str] = []
126
- stderr_messages: list[str] = []
127
126
  overall_success = False
128
127
  file_results = []
129
128
 
@@ -120,15 +120,13 @@ class generate_new_tool:
120
120
  # 注册新工具到当前的工具注册表
121
121
  success_message = f"工具 '{tool_name}' 已成功生成在 {tool_file_path}"
122
122
 
123
- registration_successful = False
124
123
  if agent:
125
124
  tool_registry = agent.get_tool_registry()
126
125
  if tool_registry:
127
126
  # 尝试加载并注册新工具
128
127
 
129
128
  if tool_registry.register_tool_by_file(str(tool_file_path)):
130
- success_message += f"\n已成功注册到当前会话的工具注册表中"
131
- registration_successful = True
129
+ success_message += "\n已成功注册到当前会话的工具注册表中"
132
130
  else:
133
131
  # 注册失败,删除已创建的文件
134
132
  PrettyOutput.print(
@@ -140,13 +138,13 @@ class generate_new_tool:
140
138
  return {
141
139
  "success": False,
142
140
  "stdout": "",
143
- "stderr": f"工具文件已生成,但注册失败。文件已被删除。",
141
+ "stderr": "工具文件已生成,但注册失败。文件已被删除。",
144
142
  }
145
143
  else:
146
144
  PrettyOutput.print(
147
145
  "未找到工具注册表,无法自动注册工具", OutputType.WARNING
148
146
  )
149
- success_message += f"\n注册到当前会话失败,可能需要重新启动Jarvis"
147
+ success_message += "\n注册到当前会话失败,可能需要重新启动Jarvis"
150
148
 
151
149
  # 检查并安装缺失的依赖
152
150
  try:
@@ -15,7 +15,6 @@ from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
15
15
  from jarvis.jarvis_mcp.streamable_mcp_client import StreamableMcpClient
16
16
  from jarvis.jarvis_tools.base import Tool
17
17
  from jarvis.jarvis_utils.config import get_data_dir, get_tool_load_dirs
18
- from jarvis.jarvis_utils.input import user_confirm
19
18
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
20
19
  from jarvis.jarvis_utils.tag import ct, ot
21
20
  from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
@@ -128,7 +127,7 @@ class ToolRegistry(OutputHandlerProtocol):
128
127
  """加载工具"""
129
128
  tools = self.get_all_tools()
130
129
  if tools:
131
- tools_prompt = f"<tools_section>\n"
130
+ tools_prompt = "<tools_section>\n"
132
131
  tools_prompt += " <header>## 可用工具:</header>\n"
133
132
  tools_prompt += " <tools_list>\n"
134
133
  for tool in tools:
@@ -202,7 +201,7 @@ class ToolRegistry(OutputHandlerProtocol):
202
201
  def _get_tool_stats(self) -> Dict[str, int]:
203
202
  """从数据目录获取工具调用统计"""
204
203
  from jarvis.jarvis_stats.stats import StatsManager
205
- from datetime import datetime, timedelta
204
+ from datetime import datetime
206
205
 
207
206
  # 获取所有工具的统计数据
208
207
  tool_stats = {}
@@ -839,6 +838,21 @@ class ToolRegistry(OutputHandlerProtocol):
839
838
  # 执行工具调用(根据工具实现的协议版本,由系统在内部决定agent的传递方式)
840
839
  result = self.execute_tool(name, args, agent)
841
840
 
841
+ # 记录本轮实际执行的工具,供上层逻辑(如记忆保存判定)使用
842
+ try:
843
+ from jarvis.jarvis_agent import Agent # 延迟导入避免循环依赖
844
+ agent_instance_for_record: Agent = agent_instance
845
+ # 记录最后一次执行的工具
846
+ agent_instance_for_record.set_user_data("__last_executed_tool__", name) # type: ignore
847
+ # 记录本轮累计执行的工具列表
848
+ executed_list = agent_instance_for_record.get_user_data("__executed_tools__") # type: ignore
849
+ if not isinstance(executed_list, list):
850
+ executed_list = []
851
+ executed_list.append(name)
852
+ agent_instance_for_record.set_user_data("__executed_tools__", executed_list) # type: ignore
853
+ except Exception:
854
+ pass
855
+
842
856
  # 格式化输出
843
857
  output = self._format_tool_output(
844
858
  result["stdout"], result.get("stderr", "")