Spaces:
Running
Running
refactor: generalize OpenClaw state persistence and add browser warm-up sequence
Browse files- start.sh +39 -36
- workspace-sync.py +59 -11
start.sh
CHANGED
|
@@ -169,25 +169,19 @@ fi
|
|
| 169 |
|
| 170 |
# ββ Restore persisted OpenClaw state (if present) ββ
|
| 171 |
STATE_BACKUP_ROOT="/home/node/.openclaw/workspace/.huggingclaw-state/openclaw"
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
echo " β
${name} restored"
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
restore_state_dir "agents"
|
| 189 |
-
restore_state_dir "memory"
|
| 190 |
-
restore_state_dir "extensions"
|
| 191 |
|
| 192 |
# ββ Restore persisted WhatsApp credentials (if present) ββ
|
| 193 |
WA_BACKUP_DIR="/home/node/.openclaw/workspace/.huggingclaw-state/credentials/whatsapp/default"
|
|
@@ -414,23 +408,9 @@ graceful_shutdown() {
|
|
| 414 |
echo "π Shutting down gracefully..."
|
| 415 |
|
| 416 |
if [ -f "/home/node/app/workspace-sync.py" ]; then
|
| 417 |
-
echo "
|
| 418 |
-
python3 /home/node/app/workspace-sync.py --
|
| 419 |
-
echo " β οΈ Could not
|
| 420 |
-
fi
|
| 421 |
-
|
| 422 |
-
# Commit any unsaved workspace changes
|
| 423 |
-
if [ -d "/home/node/.openclaw/workspace/.git" ]; then
|
| 424 |
-
echo "πΎ Saving workspace before exit..."
|
| 425 |
-
cd /home/node/.openclaw/workspace
|
| 426 |
-
git add -A 2>/dev/null
|
| 427 |
-
if ! git diff --cached --quiet 2>/dev/null; then
|
| 428 |
-
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
| 429 |
-
git commit -m "Shutdown sync ${TIMESTAMP}" 2>/dev/null
|
| 430 |
-
git push origin main 2>/dev/null && echo " β
Workspace saved!" || echo " β οΈ Push failed"
|
| 431 |
-
else
|
| 432 |
-
echo " β
No unsaved changes"
|
| 433 |
-
fi
|
| 434 |
fi
|
| 435 |
|
| 436 |
# Kill background processes
|
|
@@ -440,6 +420,26 @@ graceful_shutdown() {
|
|
| 440 |
}
|
| 441 |
trap graceful_shutdown SIGTERM SIGINT
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
# ββ Start background services ββ
|
| 444 |
export LLM_MODEL="$LLM_MODEL"
|
| 445 |
# 10. Start Health Server & Dashboard
|
|
@@ -476,6 +476,9 @@ if [ "$WHATSAPP_ENABLED_NORMALIZED" = "true" ]; then
|
|
| 476 |
echo "π‘οΈ WhatsApp Guardian started (PID: $GUARDIAN_PID)"
|
| 477 |
fi
|
| 478 |
|
|
|
|
|
|
|
|
|
|
| 479 |
# 12. Start Workspace Sync after startup settles
|
| 480 |
python3 -u /home/node/app/workspace-sync.py &
|
| 481 |
|
|
|
|
| 169 |
|
| 170 |
# ββ Restore persisted OpenClaw state (if present) ββ
|
| 171 |
STATE_BACKUP_ROOT="/home/node/.openclaw/workspace/.huggingclaw-state/openclaw"
|
| 172 |
+
if [ -d "$STATE_BACKUP_ROOT" ]; then
|
| 173 |
+
echo "π§ Restoring OpenClaw state..."
|
| 174 |
+
for source_path in "$STATE_BACKUP_ROOT"/*; do
|
| 175 |
+
[ -e "$source_path" ] || continue
|
| 176 |
+
name="$(basename "$source_path")"
|
| 177 |
+
target_path="/home/node/.openclaw/${name}"
|
| 178 |
+
|
| 179 |
+
rm -rf "$target_path"
|
| 180 |
+
mkdir -p "$(dirname "$target_path")"
|
| 181 |
+
cp -R "$source_path" "$target_path"
|
| 182 |
+
done
|
| 183 |
+
echo " β
OpenClaw state restored"
|
| 184 |
+
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
# ββ Restore persisted WhatsApp credentials (if present) ββ
|
| 187 |
WA_BACKUP_DIR="/home/node/.openclaw/workspace/.huggingclaw-state/credentials/whatsapp/default"
|
|
|
|
| 408 |
echo "π Shutting down gracefully..."
|
| 409 |
|
| 410 |
if [ -f "/home/node/app/workspace-sync.py" ]; then
|
| 411 |
+
echo "πΎ Saving OpenClaw state before exit..."
|
| 412 |
+
python3 /home/node/app/workspace-sync.py --sync-once || \
|
| 413 |
+
echo " β οΈ Could not complete shutdown sync"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
fi
|
| 415 |
|
| 416 |
# Kill background processes
|
|
|
|
| 420 |
}
|
| 421 |
trap graceful_shutdown SIGTERM SIGINT
|
| 422 |
|
| 423 |
+
warmup_browser() {
|
| 424 |
+
[ "$BROWSER_SHOULD_ENABLE" = "true" ] || return 0
|
| 425 |
+
|
| 426 |
+
(
|
| 427 |
+
sleep 5
|
| 428 |
+
|
| 429 |
+
local attempt
|
| 430 |
+
for attempt in 1 2 3 4 5; do
|
| 431 |
+
if openclaw browser --browser-profile openclaw start >/dev/null 2>&1; then
|
| 432 |
+
openclaw browser --browser-profile openclaw open about:blank >/dev/null 2>&1 || true
|
| 433 |
+
echo " β
Managed browser ready"
|
| 434 |
+
return 0
|
| 435 |
+
fi
|
| 436 |
+
sleep 2
|
| 437 |
+
done
|
| 438 |
+
|
| 439 |
+
echo " β οΈ Managed browser warm-up did not complete; first browser action may need a retry"
|
| 440 |
+
) &
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
# ββ Start background services ββ
|
| 444 |
export LLM_MODEL="$LLM_MODEL"
|
| 445 |
# 10. Start Health Server & Dashboard
|
|
|
|
| 476 |
echo "π‘οΈ WhatsApp Guardian started (PID: $GUARDIAN_PID)"
|
| 477 |
fi
|
| 478 |
|
| 479 |
+
# 11.5 Warm up the managed browser so first browser actions have a live tab
|
| 480 |
+
warmup_browser
|
| 481 |
+
|
| 482 |
# 12. Start Workspace Sync after startup settles
|
| 483 |
python3 -u /home/node/app/workspace-sync.py &
|
| 484 |
|
workspace-sync.py
CHANGED
|
@@ -19,10 +19,11 @@ OPENCLAW_HOME = Path("/home/node/.openclaw")
|
|
| 19 |
WORKSPACE = OPENCLAW_HOME / "workspace"
|
| 20 |
STATE_DIR = WORKSPACE / ".huggingclaw-state"
|
| 21 |
OPENCLAW_STATE_BACKUP_DIR = STATE_DIR / "openclaw"
|
| 22 |
-
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
"
|
|
|
|
| 26 |
}
|
| 27 |
WHATSAPP_CREDS_DIR = Path("/home/node/.openclaw/credentials/whatsapp/default")
|
| 28 |
WHATSAPP_BACKUP_DIR = STATE_DIR / "credentials" / "whatsapp" / "default"
|
|
@@ -61,16 +62,19 @@ def snapshot_state_into_workspace() -> None:
|
|
| 61 |
"""
|
| 62 |
try:
|
| 63 |
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
for
|
| 66 |
-
if
|
| 67 |
continue
|
| 68 |
|
| 69 |
-
backup_path = OPENCLAW_STATE_BACKUP_DIR / name
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
except Exception as e:
|
| 75 |
print(f" β οΈ Could not snapshot OpenClaw state: {e}")
|
| 76 |
|
|
@@ -211,6 +215,50 @@ def main():
|
|
| 211 |
write_sync_status("configured", "State snapshot refreshed during shutdown.")
|
| 212 |
return
|
| 213 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
if not WORKSPACE.exists():
|
| 215 |
print("π Workspace sync: workspace not found, exiting.")
|
| 216 |
return
|
|
|
|
| 19 |
WORKSPACE = OPENCLAW_HOME / "workspace"
|
| 20 |
STATE_DIR = WORKSPACE / ".huggingclaw-state"
|
| 21 |
OPENCLAW_STATE_BACKUP_DIR = STATE_DIR / "openclaw"
|
| 22 |
+
EXCLUDED_STATE_NAMES = {
|
| 23 |
+
"workspace",
|
| 24 |
+
"openclaw-app",
|
| 25 |
+
"gateway.log",
|
| 26 |
+
"browser",
|
| 27 |
}
|
| 28 |
WHATSAPP_CREDS_DIR = Path("/home/node/.openclaw/credentials/whatsapp/default")
|
| 29 |
WHATSAPP_BACKUP_DIR = STATE_DIR / "credentials" / "whatsapp" / "default"
|
|
|
|
| 62 |
"""
|
| 63 |
try:
|
| 64 |
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
| 65 |
+
if OPENCLAW_STATE_BACKUP_DIR.exists():
|
| 66 |
+
shutil.rmtree(OPENCLAW_STATE_BACKUP_DIR, ignore_errors=True)
|
| 67 |
+
OPENCLAW_STATE_BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
| 68 |
|
| 69 |
+
for source_path in OPENCLAW_HOME.iterdir():
|
| 70 |
+
if source_path.name in EXCLUDED_STATE_NAMES:
|
| 71 |
continue
|
| 72 |
|
| 73 |
+
backup_path = OPENCLAW_STATE_BACKUP_DIR / source_path.name
|
| 74 |
+
if source_path.is_dir():
|
| 75 |
+
shutil.copytree(source_path, backup_path)
|
| 76 |
+
elif source_path.is_file():
|
| 77 |
+
shutil.copy2(source_path, backup_path)
|
| 78 |
except Exception as e:
|
| 79 |
print(f" β οΈ Could not snapshot OpenClaw state: {e}")
|
| 80 |
|
|
|
|
| 215 |
write_sync_status("configured", "State snapshot refreshed during shutdown.")
|
| 216 |
return
|
| 217 |
|
| 218 |
+
if "--sync-once" in sys.argv:
|
| 219 |
+
if not WORKSPACE.exists():
|
| 220 |
+
print("π Workspace sync: workspace not found, exiting.")
|
| 221 |
+
return
|
| 222 |
+
|
| 223 |
+
use_hf_hub = bool(HF_TOKEN and HF_USERNAME)
|
| 224 |
+
git_dir = WORKSPACE / ".git"
|
| 225 |
+
|
| 226 |
+
if not use_hf_hub and not git_dir.exists():
|
| 227 |
+
print("π Workspace sync: no git repo and no HF credentials, skipping.")
|
| 228 |
+
return
|
| 229 |
+
|
| 230 |
+
snapshot_state_into_workspace()
|
| 231 |
+
|
| 232 |
+
if not has_changes():
|
| 233 |
+
print("π Workspace sync: no changes to persist.")
|
| 234 |
+
write_sync_status("configured", "No new state changes to sync.")
|
| 235 |
+
return
|
| 236 |
+
|
| 237 |
+
ts = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
| 238 |
+
write_sync_status("syncing", f"Shutdown sync started at {ts}")
|
| 239 |
+
|
| 240 |
+
if use_hf_hub:
|
| 241 |
+
if sync_with_hf_hub():
|
| 242 |
+
print(f"π Workspace sync (hf_hub): pushed changes ({ts})")
|
| 243 |
+
write_sync_status("success", "Shutdown sync pushed to HF Hub")
|
| 244 |
+
return
|
| 245 |
+
if sync_with_git():
|
| 246 |
+
print(f"π Workspace sync (git fallback): pushed changes ({ts})")
|
| 247 |
+
write_sync_status("success", "Shutdown sync pushed via git fallback")
|
| 248 |
+
return
|
| 249 |
+
write_sync_status("error", "Shutdown sync failed")
|
| 250 |
+
print("π Workspace sync: shutdown sync failed.")
|
| 251 |
+
return
|
| 252 |
+
|
| 253 |
+
if sync_with_git():
|
| 254 |
+
print(f"π Workspace sync (git): pushed changes ({ts})")
|
| 255 |
+
write_sync_status("success", "Shutdown sync pushed via git")
|
| 256 |
+
return
|
| 257 |
+
|
| 258 |
+
write_sync_status("error", "Shutdown sync failed")
|
| 259 |
+
print("π Workspace sync: shutdown sync failed.")
|
| 260 |
+
return
|
| 261 |
+
|
| 262 |
if not WORKSPACE.exists():
|
| 263 |
print("π Workspace sync: workspace not found, exiting.")
|
| 264 |
return
|