solana-traderclaw 1.0.111 → 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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-traderclaw",
|
|
3
|
-
"version": "1.0.
|
|
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)"
|