solana-traderclaw 1.0.110 → 1.0.112

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # Wrapper — canonical script: scripts/openclaw-session-cleanup.sh
3
+ exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scripts/openclaw-session-cleanup.sh" "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solana-traderclaw",
3
- "version": "1.0.110",
3
+ "version": "1.0.112",
4
4
  "description": "TraderClaw V1-Upgraded — Solana trading for OpenClaw with intelligence lab, tool envelopes, prompt scrubbing, read-only X social intel, and split skill docs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,8 @@
12
12
  "skills/",
13
13
  "config/",
14
14
  "lib/",
15
+ "scripts/openclaw-session-cleanup.sh",
16
+ "openclaw-truncate.sh",
15
17
  "openclaw.plugin.json",
16
18
  "README.md"
17
19
  ],
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env bash
2
+ # OpenClaw session / checkpoint cleanup (safe for cron: resolves gateway unit, fixes user-systemd env)
3
+ #
4
+ # Archives bloated heartbeat session logs + *.checkpoint.*.jsonl shards (defaults: size >= CHECKPOINT_MIN_MB),
5
+ # trims stale sessions.json keys (heartbeat pointer + alpha_stream), restarts the gateway like TraderClaw:
6
+ # systemctl --user stop|daemon-reload|start openclaw-gateway.service
7
+ # Unit name matches `resolveGatewayUnitNameFromStatusJson` when `openclaw gateway status --json` works.
8
+ #
9
+ # Typical weekly cron (root VPS, state under /root/.openclaw):
10
+ # 0 3 * * 0 OPENCLAW_STATE_DIR=/root/.openclaw /usr/local/lib/node_modules/solana-traderclaw/scripts/openclaw-session-cleanup.sh >> /var/log/openclaw-session-cleanup.log 2>&1
11
+ #
12
+ # Aggressive mode (move every checkpoint shard, not only large ones):
13
+ # STRIP_ALL_CHECKPOINTS=1 OPENCLAW_STATE_DIR=/root/.openclaw …/openclaw-session-cleanup.sh
14
+ #
15
+ # Optional env:
16
+ # OPENCLAW_STATE_DIR default: $HOME/.openclaw
17
+ # OPENCLAW_AGENT_ID default: main
18
+ # OPENCLAW_GATEWAY_UNIT default: auto from openclaw JSON, else openclaw-gateway.service
19
+ # CHECKPOINT_MIN_MB default: 10
20
+ # STRIP_ALL_CHECKPOINTS default: 0 (set 1 to archive all *.checkpoint.*.jsonl)
21
+ # ARCHIVE_RETENTION_DAYS if set, delete archive subdirs older than this many days
22
+ # DRY_RUN if 1, only print planned actions (still resolves unit; does not stop gateway)
23
+
24
+ set -euo pipefail
25
+
26
+ STATE="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
27
+ AGENT_ID="${OPENCLAW_AGENT_ID:-main}"
28
+ SESSIONS="$STATE/agents/$AGENT_ID/sessions"
29
+ ARCHIVE="$SESSIONS.archive"
30
+ MIN_MB="${CHECKPOINT_MIN_MB:-10}"
31
+ STRIP_ALL="${STRIP_ALL_CHECKPOINTS:-0}"
32
+ DRY="${DRY_RUN:-0}"
33
+ UNIT="${OPENCLAW_GATEWAY_UNIT:-}"
34
+ RETAIN_DAYS="${ARCHIVE_RETENTION_DAYS:-}"
35
+
36
+ fix_user_systemd_env_for_cron() {
37
+ if [[ -z "${XDG_RUNTIME_DIR:-}" ]] && [[ -n "${HOME:-}" ]]; then
38
+ local uid
39
+ uid="$(id -u)"
40
+ if [[ -d "/run/user/$uid" ]]; then
41
+ export XDG_RUNTIME_DIR="/run/user/$uid"
42
+ fi
43
+ fi
44
+ }
45
+
46
+ resolve_gateway_unit() {
47
+ if [[ -n "$UNIT" ]]; then
48
+ echo "$UNIT"
49
+ return
50
+ fi
51
+ if ! command -v openclaw >/dev/null 2>&1; then
52
+ echo "openclaw-gateway.service"
53
+ return
54
+ fi
55
+ local j
56
+ j="$(openclaw gateway status --json 2>/dev/null || true)"
57
+ if [[ -z "$j" ]]; then
58
+ echo "openclaw-gateway.service"
59
+ return
60
+ fi
61
+ python3 -c '
62
+ import json, sys
63
+ raw = sys.stdin.read().strip()
64
+ if not raw:
65
+ print("openclaw-gateway.service")
66
+ raise SystemExit(0)
67
+ try:
68
+ d = json.loads(raw)
69
+ except json.JSONDecodeError:
70
+ print("openclaw-gateway.service")
71
+ raise SystemExit(0)
72
+ svc = d.get("service") or {}
73
+ systemd = svc.get("systemd") or {}
74
+ u = systemd.get("unit") or ""
75
+ if isinstance(u, str) and u.endswith(".service"):
76
+ print(u)
77
+ raise SystemExit(0)
78
+ for key in ("file",):
79
+ f = svc.get(key) or ""
80
+ if isinstance(f, str) and "/" in f and f.endswith(".service"):
81
+ print(f.split("/")[-1])
82
+ raise SystemExit(0)
83
+ for key in ("file", "unitPath"):
84
+ f = systemd.get(key) or ""
85
+ if isinstance(f, str) and "/" in f and f.endswith(".service"):
86
+ print(f.split("/")[-1])
87
+ raise SystemExit(0)
88
+ print("openclaw-gateway.service")
89
+ ' <<<"$j"
90
+ }
91
+
92
+ prune_old_archives() {
93
+ [[ -n "$RETAIN_DAYS" ]] || return 0
94
+ [[ "$DRY" == "1" ]] && return 0
95
+ [[ -d "$ARCHIVE" ]] || return 0
96
+ find "$ARCHIVE" -mindepth 1 -maxdepth 1 -type d -mtime "+${RETAIN_DAYS}" -print -exec rm -rf {} +
97
+ }
98
+
99
+ ts="$(date +%Y%m%d-%H%M%S)"
100
+
101
+ echo "== OpenClaw session cleanup $ts"
102
+ echo " State dir: $STATE"
103
+ echo " Sessions: $SESSIONS"
104
+
105
+ if [[ ! -d "$SESSIONS" ]]; then
106
+ echo " No sessions directory — nothing to do."
107
+ exit 0
108
+ fi
109
+
110
+ fix_user_systemd_env_for_cron
111
+ UNIT="$(resolve_gateway_unit)"
112
+ echo " Gateway unit: $UNIT"
113
+
114
+ mkdir -p "$ARCHIVE/$ts"
115
+
116
+ stop_gateway() {
117
+ if [[ "$DRY" == "1" ]]; then
118
+ echo " [dry-run] would: systemctl --user stop $UNIT"
119
+ return
120
+ fi
121
+ systemctl --user stop "$UNIT" || true
122
+ sleep 2
123
+ }
124
+
125
+ start_gateway() {
126
+ if [[ "$DRY" == "1" ]]; then
127
+ echo " [dry-run] would: systemctl --user daemon-reload && systemctl --user start $UNIT"
128
+ return
129
+ fi
130
+ systemctl --user daemon-reload 2>/dev/null || true
131
+ systemctl --user start "$UNIT"
132
+ sleep 3
133
+ systemctl --user is-active "$UNIT" || {
134
+ echo " WARN: gateway not active — check: journalctl --user -u $UNIT -n 80 --no-pager" >&2
135
+ return 1
136
+ }
137
+ }
138
+
139
+ backup_registry() {
140
+ local reg="$SESSIONS/sessions.json"
141
+ if [[ ! -f "$reg" ]]; then
142
+ echo " No sessions.json — skipping registry backup."
143
+ return
144
+ fi
145
+ if [[ "$DRY" == "1" ]]; then
146
+ echo " [dry-run] would backup $reg"
147
+ return
148
+ fi
149
+ cp -a "$reg" "$SESSIONS/sessions.json.bak.$ts"
150
+ echo " Registry backup: sessions.json.bak.$ts"
151
+ }
152
+
153
+ archive_heartbeat_and_checkpoints() {
154
+ export SESSIONS ARCHIVE_BASE="$ARCHIVE" ARCHIVE_TS="$ts" DRY STRIP_ALL MIN_MB OPENCLAW_AGENT_ID="$AGENT_ID"
155
+ python3 <<'PY'
156
+ import json, os, shutil, sys
157
+
158
+ sessions = os.environ["SESSIONS"]
159
+ archive_ts = os.environ["ARCHIVE_TS"]
160
+ dry = os.environ["DRY"] == "1"
161
+ strip_all = os.environ["STRIP_ALL"] == "1"
162
+ min_mb = int(os.environ["MIN_MB"])
163
+ agent = os.environ["OPENCLAW_AGENT_ID"]
164
+
165
+ dest = os.path.join(os.environ["ARCHIVE_BASE"], archive_ts)
166
+ os.makedirs(dest, exist_ok=True)
167
+
168
+ reg_path = os.path.join(sessions, "sessions.json")
169
+ hb_sid = ""
170
+
171
+ if os.path.isfile(reg_path):
172
+ try:
173
+ with open(reg_path, encoding="utf-8") as f:
174
+ r = json.load(f)
175
+ e = r.get(f"agent:{agent}:main:heartbeat") or r.get("agent:main:main:heartbeat")
176
+ if e and isinstance(e.get("sessionId"), str):
177
+ hb_sid = e["sessionId"]
178
+ except Exception as ex:
179
+ print(f" WARN: could not read registry: {ex}", file=sys.stderr)
180
+
181
+ def move_if_exists(src):
182
+ if not os.path.isfile(src):
183
+ return
184
+ base = os.path.basename(src)
185
+ dst = os.path.join(dest, base)
186
+ if dry:
187
+ print(f" [dry-run] would mv {src} -> {dst}")
188
+ return
189
+ shutil.move(src, dst)
190
+ print(f" archived {base}")
191
+
192
+ if hb_sid:
193
+ move_if_exists(os.path.join(sessions, f"{hb_sid}.jsonl"))
194
+ for name in os.listdir(sessions):
195
+ if name.startswith(hb_sid + ".checkpoint.") and name.endswith(".jsonl"):
196
+ move_if_exists(os.path.join(sessions, name))
197
+
198
+ min_bytes = max(min_mb, 0) * 1024 * 1024
199
+ for name in os.listdir(sessions):
200
+ if not (name.endswith(".jsonl") and ".checkpoint." in name):
201
+ continue
202
+ path = os.path.join(sessions, name)
203
+ try:
204
+ st = os.stat(path)
205
+ except OSError:
206
+ continue
207
+ if strip_all or st.st_size >= min_bytes:
208
+ move_if_exists(path)
209
+
210
+ print(f" Archive batch: {dest}")
211
+ PY
212
+ }
213
+
214
+ clean_stale_registry_keys() {
215
+ local reg="$SESSIONS/sessions.json"
216
+ [[ -f "$reg" ]] || return 0
217
+ if [[ "$DRY" == "1" ]]; then
218
+ echo " [dry-run] would trim heartbeat + alpha_stream keys in sessions.json"
219
+ return
220
+ fi
221
+ export REG="$reg" OPENCLAW_AGENT_ID="$AGENT_ID"
222
+ python3 <<'PY'
223
+ import json, os
224
+ agent = os.environ["OPENCLAW_AGENT_ID"]
225
+ reg = os.environ["REG"]
226
+ hb_key = f"agent:{agent}:main:heartbeat"
227
+ with open(reg, encoding="utf-8") as f:
228
+ r = json.load(f)
229
+ n0 = len(r)
230
+ r.pop(hb_key, None)
231
+ r.pop("agent:main:main:heartbeat", None)
232
+ for k in [k for k in list(r.keys()) if "alpha_stream" in k]:
233
+ r.pop(k, None)
234
+ tmp = reg + ".tmp"
235
+ with open(tmp, "w", encoding="utf-8") as f:
236
+ json.dump(r, f, indent=2)
237
+ os.replace(tmp, reg)
238
+ print(f" Registry entries: {n0} -> {len(r)}")
239
+ PY
240
+ }
241
+
242
+ prune_old_archives
243
+
244
+ stop_gateway
245
+ backup_registry
246
+ archive_heartbeat_and_checkpoints
247
+ clean_stale_registry_keys
248
+
249
+ if [[ "$DRY" != "1" ]]; then
250
+ echo " Archived size:"
251
+ du -sh "$ARCHIVE/$ts" 2>/dev/null || true
252
+ fi
253
+
254
+ start_gateway
255
+
256
+ echo "OK — heartbeat will open a fresh session on the next tick."
257
+ echo " Archive: $ARCHIVE/$ts (delete old archives when comfortable)"