openclaw-diag-cli 0.1.3 → 0.2.2
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.
- package/README.md +83 -71
- package/bin/ocdiag +0 -1
- package/bin/openclaw-diag.js +65 -176
- package/diag/01_sys_health.py +0 -2
- package/diag/02_environment.py +32 -6
- package/diag/03_configuration.py +4 -1
- package/diag/04_gateway.py +30 -8
- package/diag/05_recent_errors.py +24 -14
- package/diag/06_cron_jobs.py +4 -41
- package/diag/07_performance.py +114 -42
- package/diag/08_sessions.py +2 -54
- package/diag/09_plugin_diag.py +52 -25
- package/diag/10_shell_history.py +28 -10
- package/lib/__pycache__/bundle.cpython-310.pyc +0 -0
- package/lib/bundle.py +6 -13
- package/ocdiag/__init__.py +1 -1
- package/ocdiag/__pycache__/__init__.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/cli.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/dispatcher.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/doctor.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/jsonlog.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/output.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/paths.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/recent_logs.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/sensitive.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/sessions.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/timeutil.cpython-310.pyc +0 -0
- package/ocdiag/__pycache__/tokens.cpython-310.pyc +0 -0
- package/ocdiag/cli.py +16 -1
- package/ocdiag/dispatcher.py +140 -53
- package/ocdiag/doctor.py +162 -0
- package/ocdiag/jsonlog.py +0 -5
- package/ocdiag/paths.py +0 -17
- package/ocdiag/recent_logs.py +0 -3
- package/ocdiag/sensitive.py +95 -1
- package/ocdiag/sessions.py +161 -0
- package/ocdiag/timeutil.py +0 -11
- package/ocdiag/tokens.py +0 -4
- package/package.json +2 -2
- package/tools/oc_session_extract.py +190 -67
- package/tools/oc_session_trace.py +48 -46
|
@@ -21,7 +21,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
21
21
|
|
|
22
22
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
23
23
|
|
|
24
|
-
from ocdiag import paths
|
|
24
|
+
from ocdiag import paths, sessions
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
DEFAULT_BASE_DIR = paths.SESSIONS_BASE
|
|
@@ -52,14 +52,6 @@ def fmt_duration(ms: float) -> str:
|
|
|
52
52
|
return f"{m}m{s:.1f}s"
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def human_size(n: int) -> str:
|
|
56
|
-
for unit in ("B", "KB", "MB", "GB"):
|
|
57
|
-
if n < 1024:
|
|
58
|
-
return f"{n:.1f} {unit}" if unit != "B" else f"{n} {unit}"
|
|
59
|
-
n /= 1024
|
|
60
|
-
return f"{n:.1f} TB"
|
|
61
|
-
|
|
62
|
-
|
|
63
55
|
def extract_text(content: Any) -> str:
|
|
64
56
|
if isinstance(content, str):
|
|
65
57
|
return content
|
|
@@ -75,41 +67,25 @@ def extract_text(content: Any) -> str:
|
|
|
75
67
|
return str(content)
|
|
76
68
|
|
|
77
69
|
|
|
78
|
-
def
|
|
70
|
+
def resolve_session_file(
|
|
79
71
|
session_id: str,
|
|
80
72
|
base_dir: str = DEFAULT_BASE_DIR,
|
|
81
73
|
agent: Optional[str] = None,
|
|
82
|
-
) -> Optional[str]:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
continue
|
|
98
|
-
full = os.path.join(sd, entry)
|
|
99
|
-
if not os.path.isfile(full):
|
|
100
|
-
continue
|
|
101
|
-
if entry == f"{session_id}.jsonl":
|
|
102
|
-
candidates.append((full, "active"))
|
|
103
|
-
elif ".jsonl.deleted." in entry:
|
|
104
|
-
candidates.append((full, "deleted"))
|
|
105
|
-
elif ".jsonl.reset." in entry:
|
|
106
|
-
candidates.append((full, "reset"))
|
|
107
|
-
elif ".jsonl.bak-" in entry:
|
|
108
|
-
candidates.append((full, "backup"))
|
|
109
|
-
|
|
110
|
-
prio = {"active": 0, "deleted": 1, "reset": 2, "backup": 3}
|
|
111
|
-
candidates.sort(key=lambda x: prio.get(x[1], 9))
|
|
112
|
-
return candidates[0][0] if candidates else None
|
|
74
|
+
) -> Tuple[Optional[str], List[str]]:
|
|
75
|
+
"""Resolve UUID-or-prefix to a single session file path.
|
|
76
|
+
|
|
77
|
+
Returns ``(path, candidates)``. ``path`` is None on miss or ambiguity;
|
|
78
|
+
``candidates`` is non-empty only when the prefix matched multiple
|
|
79
|
+
distinct session UUIDs.
|
|
80
|
+
"""
|
|
81
|
+
files, candidates = sessions.resolve(
|
|
82
|
+
session_id, base_dir=base_dir, agent=agent, include_transient=False,
|
|
83
|
+
)
|
|
84
|
+
if candidates:
|
|
85
|
+
return None, candidates
|
|
86
|
+
if not files:
|
|
87
|
+
return None, []
|
|
88
|
+
return files[0][0], []
|
|
113
89
|
|
|
114
90
|
|
|
115
91
|
def find_trajectory_file(session_file: str) -> Optional[str]:
|
|
@@ -634,6 +610,7 @@ def format_json(session_id, session_file, user_msg_index, user_msg_id, analysis,
|
|
|
634
610
|
|
|
635
611
|
def main():
|
|
636
612
|
parser = argparse.ArgumentParser(
|
|
613
|
+
prog=os.environ.get("OPENCLAW_DIAG_PROG") or None,
|
|
637
614
|
description="Trace the processing timeline of a user message in an OpenClaw session.",
|
|
638
615
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
639
616
|
)
|
|
@@ -650,12 +627,37 @@ def main():
|
|
|
650
627
|
parser.add_argument("--json", action="store_true", help="Output as structured JSON")
|
|
651
628
|
args = parser.parse_args()
|
|
652
629
|
|
|
653
|
-
|
|
630
|
+
ok, msg = sessions.is_valid_query(args.session_id)
|
|
631
|
+
if not ok:
|
|
632
|
+
print(f"Error: {msg}", file=sys.stderr)
|
|
633
|
+
sys.exit(2)
|
|
634
|
+
session_file, candidates = resolve_session_file(
|
|
635
|
+
args.session_id, args.base_dir, args.agent,
|
|
636
|
+
)
|
|
637
|
+
if candidates:
|
|
638
|
+
print(
|
|
639
|
+
f"Error: 前缀 '{args.session_id}' 匹配多个 session(请补长前缀):",
|
|
640
|
+
file=sys.stderr,
|
|
641
|
+
)
|
|
642
|
+
for sid in candidates:
|
|
643
|
+
print(f" {sid}", file=sys.stderr)
|
|
644
|
+
sys.exit(1)
|
|
654
645
|
if not session_file:
|
|
655
|
-
print(f"Error:
|
|
646
|
+
print(f"Error: 找不到 session '{args.session_id}'(在 {args.base_dir} 下)",
|
|
656
647
|
file=sys.stderr)
|
|
648
|
+
suggestions = sessions.recent_session_ids(args.base_dir, limit=5)
|
|
649
|
+
if suggestions:
|
|
650
|
+
print(f" 最近的 5 个 session:", file=sys.stderr)
|
|
651
|
+
for sid in suggestions:
|
|
652
|
+
print(f" {sid}", file=sys.stderr)
|
|
653
|
+
print(f" 提示:UUID 完整 36 位,前缀也可(至少 8 位)。", file=sys.stderr)
|
|
657
654
|
sys.exit(1)
|
|
658
655
|
|
|
656
|
+
# If the user passed a prefix, recover the full UUID from the resolved
|
|
657
|
+
# filename so log lookups and JSON output use the canonical id.
|
|
658
|
+
resolved_basename = os.path.basename(session_file)
|
|
659
|
+
full_session_id = resolved_basename.split(".jsonl", 1)[0]
|
|
660
|
+
|
|
659
661
|
records = load_records(session_file)
|
|
660
662
|
if not records:
|
|
661
663
|
print(f"Error: session file is empty: {session_file}", file=sys.stderr)
|
|
@@ -686,13 +688,13 @@ def main():
|
|
|
686
688
|
if not args.no_log:
|
|
687
689
|
log_files = find_gateway_logs(args.log_dir)
|
|
688
690
|
if log_files:
|
|
689
|
-
gw_info = load_gateway_timing(log_files,
|
|
691
|
+
gw_info = load_gateway_timing(log_files, full_session_id, analysis["base_epoch_ms"])
|
|
690
692
|
|
|
691
693
|
if args.json:
|
|
692
|
-
out_str = format_json(
|
|
694
|
+
out_str = format_json(full_session_id, session_file, user_msg_ordinal,
|
|
693
695
|
user_msg_id, analysis, traj_info, gw_info)
|
|
694
696
|
else:
|
|
695
|
-
out_str = format_text(
|
|
697
|
+
out_str = format_text(full_session_id, user_msg_ordinal, user_msg_id,
|
|
696
698
|
analysis, traj_info, gw_info)
|
|
697
699
|
|
|
698
700
|
if args.output:
|