apiVersion: v1 kind: ConfigMap metadata: name: openclaw-scripts namespace: openclaw labels: app.kubernetes.io/name: openclaw app.kubernetes.io/part-of: openclaw data: bootstrap-deps.sh: "#!/bin/sh\nset -e\n\necho \"=== OpenClaw Dependency & Runtime\ \ Bootstrap ===\"\nexport PATH=\"/home/node/.openclaw/bin:/home/node/.openclaw/.local/bin:$PATH\"\ \n\nSCRIPTS_DIR=\"/home/node/.openclaw/scripts\"\nmkdir -p /home/node/.openclaw/bin\ \ \"$SCRIPTS_DIR\" /home/node/.openclaw/.config\ntouch /home/node/.openclaw/.config/keep\n\ \n# 1. Sanitize legacy config keys in openclaw.json\nnode -e '\n const fs = require(\"\ fs\");\n for (const file of [\"/home/node/.openclaw/openclaw.json\", \"/home/openclaw/.openclaw/openclaw.json\"\ ]) {\n if (fs.existsSync(file)) {\n try {\n const cfg = JSON.parse(fs.readFileSync(file,\ \ \"utf8\"));\n if (cfg.browser) {\n delete cfg.browser.remoteCdpTimeoutMs;\n\ \ if (cfg.browser.profiles) {\n if (cfg.browser.profiles.default)\ \ delete cfg.browser.profiles.default.color;\n if (cfg.browser.profiles.chrome)\ \ delete cfg.browser.profiles.chrome;\n }\n }\n if (cfg.gateway\ \ && cfg.gateway.controlUi) {\n delete cfg.gateway.controlUi.dangerouslyDisableDeviceAuth;\n\ \ }\n if (!cfg.agents) cfg.agents = {};\n cfg.agents.ownership\ \ = \"explicit\";\n if (cfg.agents.entries && cfg.agents.entries.main)\ \ {\n delete cfg.agents.entries.main.default;\n }\n fs.writeFileSync(file,\ \ JSON.stringify(cfg, null, 2));\n console.log(\"Sanitized config file:\"\ , file);\n } catch (e) {\n console.error(\"Failed to sanitize\", file,\ \ e.message);\n }\n }\n }\n' || true\n\n# 2. Run pre-flight session recovery\n\ echo \"Running pre-flight self-healing...\"\npython3 \"$SCRIPTS_DIR/recover_sessions.py\"\ \ || true\n\n# 3. Doctor fix\necho \"Running openclaw doctor --fix to migrate\ \ runtime config on PVC...\"\nopenclaw doctor --fix || true\n\n# 4. Garmin OAuth\ \ tokens bootstrap / preserve\nif [ ! -s /home/node/.openclaw/garmin_tokens/garmin_tokens.json\ \ ] && [ -n \"$GARMIN_TOKENS_BASE64\" ]; then\n echo \"Bootstrapping initial\ \ Garmin OAuth tokens from secret...\"\n mkdir -p /home/node/.openclaw/garmin_tokens\n\ \ echo \"$GARMIN_TOKENS_BASE64\" | base64 -d > /home/node/.openclaw/garmin_tokens/garmin_tokens.json\n\ \ chmod 700 /home/node/.openclaw/garmin_tokens\n chmod 600 /home/node/.openclaw/garmin_tokens/garmin_tokens.json\n\ elif [ -s /home/node/.openclaw/garmin_tokens/garmin_tokens.json ]; then\n echo\ \ \"Preserving live Garmin OAuth tokens on PVC...\"\n chmod 700 /home/node/.openclaw/garmin_tokens\n\ \ chmod 600 /home/node/.openclaw/garmin_tokens/garmin_tokens.json\nfi\n\n# 5.\ \ Cleanup legacy binaries and superseded local skills\nrm -rf /home/node/.openclaw/bin/whisper*\ \ /home/node/.openclaw/bin/.whisper* /home/node/.openclaw/scripts/whisper* /home/node/.openclaw/models/whisper\ \ || true\nrm -rf /home/node/.openclaw/skills/memobird /home/node/.openclaw/skills/nerd-report\ \ /home/node/.openclaw/skills/rvv-regensburg /home/node/.openclaw/skills/skill-creator\ \ || true\nrm -rf /home/node/.openclaw/workspace/skills /home/node/.openclaw/workspace-koch/skills\ \ /home/node/.openclaw/workspace-recherche/skills || true\n\n# 6. Ensure tools.allow\ \ and models.providers.openrouter.apiKey unset\nopenclaw config unset tools.allow\ \ || true\nopenclaw config unset models.providers.openrouter.apiKey || true\n\ openclaw config unset agents.entries.mila_tracker.tools.profile || true\nopenclaw\ \ config unset agents.entries.nutrition_tracker.tools.profile || true\n\n# 7.\ \ Decouple hardlinked bootstrap files\nfor dir in /home/node/.openclaw/workspace-*;\ \ do\n if [ -d \"$dir\" ]; then\n for file in USER.md MEMORY.md; do\n \ \ if [ -f \"/home/node/.openclaw/workspace/$file\" ]; then\n cp --remove-destination\ \ \"/home/node/.openclaw/workspace/$file\" \"$dir/$file\"\n chown node:node\ \ \"$dir/$file\" 2>/dev/null || true\n chmod 664 \"$dir/$file\" 2>/dev/null\ \ || true\n fi\n done\n fi\ndone\n\n# 8. Install CLI binaries\nif [ !\ \ -f \"/home/node/.openclaw/bin/openhue.real\" ]; then\n echo \"Installing openhue\ \ CLI binary...\"\n curl -sL https://github.com/openhue/openhue-cli/releases/download/0.24/openhue_Linux_x86_64.tar.gz\ \ | tar -xz -C /home/node/.openclaw/bin openhue\n mv /home/node/.openclaw/bin/openhue\ \ /home/node/.openclaw/bin/openhue.real\n printf '#!/bin/sh\\nexport HOME=/home/node/.openclaw\\\ nexec /home/node/.openclaw/bin/openhue.real \"$@\"\\n' > /home/node/.openclaw/bin/openhue\n\ \ chmod +x /home/node/.openclaw/bin/openhue /home/node/.openclaw/bin/openhue.real\n\ fi\n\nif [ ! -f \"/home/node/.openclaw/bin/summarize\" ]; then\n echo \"Installing\ \ summarize CLI via npm...\"\n npm install -g --prefix /home/node/.openclaw @steipete/summarize\ \ || true\nfi\n\nif [ ! -f \"/home/node/.openclaw/bin/mcp-server-github\" ]; then\n\ \ echo \"Installing @modelcontextprotocol/server-github via npm...\"\n npm install\ \ -g --prefix /home/node/.openclaw @modelcontextprotocol/server-github || true\n\ fi\n\nif [ ! -f \"/home/node/.openclaw/bin/kubectl\" ]; then\n echo \"Installing\ \ kubectl CLI binary...\"\n curl -fsSL https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl\ \ -o /home/node/.openclaw/bin/kubectl\n chmod +x /home/node/.openclaw/bin/kubectl\n\ fi\n\nif [ ! -f \"/home/node/.openclaw/bin/jq\" ]; then\n echo \"Installing jq\ \ CLI binary...\"\n curl -fsSL https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64\ \ -o /home/node/.openclaw/bin/jq\n chmod +x /home/node/.openclaw/bin/jq\nfi\n\ \nif [ ! -f \"/home/node/.openclaw/bin/file\" ]; then\n echo \"Setting up file\ \ CLI helper...\"\n cp \"$SCRIPTS_DIR/file_cli.py\" /home/node/.openclaw/bin/file\n\ \ chmod +x /home/node/.openclaw/bin/file\nfi\n\nif [ -x \"/home/node/.openclaw/.local/bin/python3\"\ \ ]; then\n /home/node/.openclaw/bin/uv pip install --break-system-packages --python\ \ /home/node/.openclaw/.local/bin/python3 pillow google-api-python-client pypdf\ \ || true\nfi\n\n# 9. Clone or pull infrapuzzle repository to PVC\nmkdir -p /home/node/.openclaw/repos\n\ if [ ! -d \"/home/node/.openclaw/repos/infrapuzzle/.git\" ]; then\n echo \"Cloning\ \ infrapuzzle repository to PVC...\"\n git clone --depth 1 \"https://${GIT_SYNC_USERNAME}:${GIT_SYNC_PASSWORD}@git.moritzgraf.de/moritz/infrapuzzle.git\"\ \ /home/node/.openclaw/repos/infrapuzzle || true\nelse\n echo \"Pulling latest\ \ infrapuzzle commits on PVC...\"\n git -C /home/node/.openclaw/repos/infrapuzzle\ \ pull origin master || git -C /home/node/.openclaw/repos/infrapuzzle pull origin\ \ main || true\nfi\n\n# 10. Seed and maintain persistent mcp-venv\nif [ ! -d \"\ /home/node/.openclaw/mcp-venv\" ]; then\n echo \"Seeding persistent mcp-venv...\"\ \n uv venv --python 3.12 /home/node/.openclaw/mcp-venv\n uv pip install --python\ \ /home/node/.openclaw/mcp-venv \\\n prometheus-mcp-server \\\n ha-mcp \\\ \n chuk-mcp-open-meteo \\\n gitea-mcp \\\n git+https://github.com/Taxuspt/garmin_mcp\ \ \\\n \"mcp>=2.0\" \\\n filetype \\\n google-api-python-client \\\n\ \ google-auth \\\n bring-api \\\n requests \\\n beautifulsoup4 \\\n\ \ youtube-transcript-api\nfi\n\nif [ -d \"/home/node/.openclaw/mcp-venv\" ];\ \ then\n python3 -c 'import os; p=\"/home/node/.openclaw/mcp-venv/lib/python3.12/site-packages/mcp/server/fastmcp.py\"\ ; open(p, \"w\").write(\"import os\\nfrom fastmcp import FastMCP as _FastMCP,\ \ Context\\n\\nclass FastMCP(_FastMCP):\\n def __init__(self, name=\\\"MCP\ \ Server\\\", host=None, port=None, **kwargs):\\n if host is not None:\ \ os.environ[\\\"FASTMCP_HOST\\\"] = str(host)\\n if port is not None:\ \ os.environ[\\\"FASTMCP_PORT\\\"] = str(port)\\n super().__init__(name=name,\ \ **kwargs)\\n\")' || true\n python3 \"$SCRIPTS_DIR/patch_garmin_mcp.py\" ||\ \ true\nfi\n\n# 11. Extensions & Plugins\nif [ ! -d \"/home/node/.openclaw/extensions/perplexity\"\ \ ]; then\n echo \"Installing perplexity plugin...\"\n openclaw plugins install\ \ clawhub:@openclaw/perplexity-plugin --accept-capabilities || true\nfi\necho\ \ \"Enabling memory-wiki plugin...\"\nopenclaw plugins enable memory-wiki || true\n\ \necho \"Patching perplexity web search runtime for chat completions compatibility...\"\ \nnode \"$SCRIPTS_DIR/patch_perplexity.mjs\" || true\n\n# 12. Ensure OpenRouter\ \ auth profile in state DB\necho \"Ensuring OpenRouter auth profile in state DB...\"\ \nnode --input-type=module -e '\n import { DatabaseSync } from \"node:sqlite\"\ ;\n const key = process.env.OPENROUTER_API_KEY;\n if (key) {\n try {\n \ \ const dbPath = \"/home/node/.openclaw/state/openclaw.sqlite\";\n const\ \ db = new DatabaseSync(dbPath);\n db.prepare(\"CREATE TABLE IF NOT EXISTS\ \ config_machine_state (state_key TEXT PRIMARY KEY, value_json TEXT NOT NULL,\ \ updated_at_ms INTEGER NOT NULL)\").run();\n const row = db.prepare(\"SELECT\ \ value_json FROM config_machine_state WHERE state_key = \\x27authProfiles.store\\\ x27\").get();\n let store = row?.value_json ? JSON.parse(row.value_json)\ \ : { version: 1, profiles: {} };\n if (!store.profiles) store.profiles =\ \ {};\n store.profiles[\"openrouter:default\"] = {\n type: \"api_key\"\ ,\n provider: \"openrouter\",\n key: key\n };\n db.prepare(\"\ INSERT INTO config_machine_state (state_key, value_json, updated_at_ms) VALUES\ \ (\\x27authProfiles.store\\x27, ?, ?) ON CONFLICT(state_key) DO UPDATE SET value_json\ \ = excluded.value_json, updated_at_ms = excluded.updated_at_ms\")\n .run(JSON.stringify(store),\ \ Date.now());\n console.log(\"Synchronized openrouter auth profile in state\ \ DB.\");\n } catch (e) {\n console.error(\"Could not sync auth profile:\"\ , e.message);\n }\n }\n' || true\n\n# 13. Pre-warm SQLite page cache\necho\ \ \"Pre-warming SQLite database files into OS page cache for instant cold start...\"\ \ncat /home/node/.openclaw/state/openclaw.sqlite /home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\ \ > /dev/null 2>&1 || true\n\necho \"=== Bootstrap Complete ===\"\n" gateway-preflight.mjs: " import fs from \"node:fs\";\n import\ \ path from \"node:path\";\n import { execSync, spawn } from \"node:child_process\"\ ;\n\n if (!process.env.__OPENCLAW_PREFLIGHT_DONE) {\n process.env.__OPENCLAW_PREFLIGHT_DONE\ \ = \"1\";\n\n const isGateway = process.argv.some(arg => arg.endsWith(\"\ openclaw.mjs\") || arg.endsWith(\"openclaw\")) && process.argv.includes(\"gateway\"\ );\n\n if (isGateway) {\n const t0 = Date.now();\n \ \ console.log(\"[gateway-preflight] Initializing container self-healing\ \ preflight...\");\n\n // 1. Clean up stale gateway lock files from\ \ ungraceful kills\n for (const dir of [\"/tmp/openclaw-1000\", \"\ /tmp\"]) {\n if (fs.existsSync(dir)) {\n try {\n\ \ const files = fs.readdirSync(dir);\n for\ \ (const file of files) {\n if (file.startsWith(\"gateway\"\ ) && file.endsWith(\".lock\")) {\n const fullPath = path.join(dir,\ \ file);\n fs.unlinkSync(fullPath);\n \ \ console.log(`[gateway-preflight] Removed stale lock: ${fullPath}`);\n\ \ }\n }\n } catch (e)\ \ {\n console.warn(`[gateway-preflight] Lock cleanup warning\ \ in ${dir}:`, e.message);\n }\n }\n \ \ }\n\n // 2. Clear false database quarantines\n \ \ const qPath = \"/home/node/.openclaw/state/openclaw-quarantine.sqlite\";\n\ \ if (fs.existsSync(qPath)) {\n try {\n \ \ execSync(`/home/node/.openclaw/.local/bin/python3 -c \"import sqlite3;\ \ c=sqlite3.connect(\\x27${qPath}\\x27); c.execute(\\x27DELETE FROM quarantined_databases\\\ x27); c.commit(); c.close()\"`, { stdio: \"ignore\" });\n console.log(\"\ [gateway-preflight] Cleared database quarantine.\");\n } catch\ \ (_) {}\n }\n\n // 3. Deduplicate subagent vector databases\ \ (keeps non-main agent DBs < 15MB)\n try {\n execSync(\"\ /home/node/.openclaw/.local/bin/python3 /home/node/.openclaw/scripts/dedup_subagent_vectors.py\"\ , { stdio: \"inherit\" });\n } catch (e) {\n console.warn(\"\ [gateway-preflight] Vector deduplication warning:\", e.message);\n \ \ }\n\n // 4. Pre-warm page cache sequentially (transforms random\ \ 70 IOPS disk reads into sequential streaming)\n try {\n \ \ const pw0 = Date.now();\n execSync(\"cat /home/node/.openclaw/state/openclaw.sqlite\ \ /home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite > /dev/null 2>&1 ||\ \ true\");\n console.log(`[gateway-preflight] Pre-warmed SQLite\ \ databases in ${Date.now() - pw0}ms`);\n } catch (_) {}\n\n \ \ // 5. Recover any wedged running sessions left over from ungraceful\ \ container restart\n try {\n execSync(\"/home/node/.openclaw/.local/bin/python3\ \ /home/node/.openclaw/scripts/recover_sessions.py\", { stdio: \"inherit\" });\n\ \ } catch (e) {\n console.warn(\"[gateway-preflight]\ \ Session recovery warning:\", e.message);\n }\n\n //\ \ 6. Spawn background turn replay daemon if interrupted turns were detected\n\ \ try {\n if (fs.existsSync(\"/tmp/openclaw-interrupted-turns.json\"\ )) {\n console.log(\"[gateway-preflight] Spawning background\ \ turn replay daemon...\");\n const child = spawn(\"/home/node/.openclaw/.local/bin/python3\"\ , [\"/home/node/.openclaw/scripts/replay_interrupted_turns.py\"], {\n \ \ detached: true,\n stdio: \"ignore\"\n \ \ });\n child.unref();\n }\n \ \ } catch (e) {\n console.warn(\"[gateway-preflight] Could\ \ not spawn turn replay daemon:\", e.message);\n }\n\n \ \ console.log(`[gateway-preflight] Self-healing completed in ${Date.now() -\ \ t0}ms.`);\n }\n }\n" recover_sessions.py: " #!/usr/bin/env python3\n import glob, sqlite3,\ \ json, os, sys, time\n\n t0 = time.time()\n recovered = 0\n\ \ interrupted_turns = []\n\n def extract_prompt(msg):\n \ \ if not msg: return \"\"\n content = msg.get(\"content\"\ )\n if isinstance(content, str): return content\n if\ \ isinstance(content, list):\n parts = []\n \ \ for c in content:\n if isinstance(c, dict): parts.append(c.get(\"\ text\") or c.get(\"content\") or \"\")\n elif isinstance(c,\ \ str): parts.append(c)\n return \"\\n\".join(filter(None, parts))\n\ \ return str(content or \"\")\n\n for db in sorted(glob.glob(\"\ /home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\")):\n \ \ try:\n conn = sqlite3.connect(db, timeout=10.0)\n \ \ cursor = conn.cursor()\n while True:\n \ \ cursor.execute(\"PRAGMA foreign_key_check;\")\n \ \ violations = cursor.fetchall()\n if not violations: break\n\ \ for table, rowid, parent, fkid in violations:\n \ \ cursor.execute(f\"DELETE FROM {table} WHERE rowid = ?\", (rowid,))\n\ \ cursor.execute(\"SELECT session_key, entry_json, current_session_id\ \ FROM session_nodes WHERE status = \\\"running\\\"\")\n rows\ \ = cursor.fetchall()\n agent_id = os.path.basename(os.path.dirname(os.path.dirname(db)))\n\ \ for sk, ej, csid in rows:\n try:\n \ \ p = json.loads(ej) if ej else {}\n \ \ sid = csid or p.get(\"sessionId\")\n if sid:\n\ \ ev = cursor.execute(\"SELECT seq, event_json FROM\ \ transcript_events WHERE session_id = ? ORDER BY seq DESC LIMIT 1\", (sid,)).fetchone()\n\ \ if ev:\n ev_seq,\ \ ev_raw = ev\n try:\n \ \ ev_data = json.loads(ev_raw)\n \ \ msg = ev_data.get(\"message\") or ev_data\n \ \ if msg.get(\"role\") == \"user\":\n \ \ prompt_text = extract_prompt(msg)\n \ \ ctx = p.get(\"restartRecoveryDeliveryContext\") or p.get(\"delivery\"\ , {}).get(\"context\") or p.get(\"delivery\", {}).get(\"route\", {}).get(\"context\"\ ) or {}\n interrupted_turns.append({\n\ \ \"agent\": agent_id,\n \ \ \"session_key\": sk,\n \ \ \"session_id\": sid,\n \ \ \"context\": ctx,\n \ \ \"prompt\": prompt_text\n \ \ })\n cursor.execute(\"PRAGMA foreign_keys\ \ = ON\")\n cursor.execute(\"DELETE FROM\ \ transcript_events WHERE session_id = ? AND seq = ?\", (sid, ev_seq))\n \ \ except Exception as ex:\n \ \ print(f\"[recover_sessions] Error parsing event: {ex}\", file=sys.stderr)\n\ \ p[\"status\"] = \"done\"\n \ \ p[\"activeWriterRunId\"] = None\n p[\"activeClaimRunId\"\ ] = None\n p[\"restartRecoveryDeliveryRunId\"] = None\n\ \ cursor.execute(\"UPDATE session_nodes SET status =\ \ \\\"done\\\", entry_json = ? WHERE session_key = ?\", (json.dumps(p), sk))\n\ \ cursor.execute(\"UPDATE session_nodes SET entry_valid\ \ = 1 WHERE session_key = ?\", (sk,))\n recovered +=\ \ 1\n except Exception: pass\n cursor.execute(\"\ UPDATE session_windows SET status = \\\"done\\\" WHERE status = \\\"running\\\"\ \")\n conn.commit()\n conn.close()\n \ \ except Exception as e:\n print(f\"[recover_sessions]\ \ Warning for {db}: {e}\", file=sys.stderr)\n\n if interrupted_turns:\n\ \ try:\n with open(\"/tmp/openclaw-interrupted-turns.json\"\ , \"w\") as f:\n json.dump(interrupted_turns, f, indent=2)\n\ \ print(f\"[recover_sessions] Detected {len(interrupted_turns)}\ \ interrupted user turn(s) saved to /tmp/openclaw-interrupted-turns.json\")\n\ \ except Exception as e:\n print(f\"[recover_sessions]\ \ Error writing interrupted turns: {e}\", file=sys.stderr)\n\n print(f\"\ [recover_sessions] Completed in {time.time() - t0:.2f}s ({recovered} wedged sessions\ \ recovered)\")\n" dedup_subagent_vectors.py: " #!/usr/bin/env python3\n import glob,\ \ os, sqlite3, sys\n\n vec_path = \"/app/node_modules/sqlite-vec-linux-x64/vec0.so\"\ \n for db in sorted(glob.glob(\"/home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\"\ )):\n agent = os.path.basename(os.path.dirname(os.path.dirname(db)))\n\ \ if agent == \"main\":\n continue\n \ \ try:\n conn = sqlite3.connect(db, timeout=10.0)\n \ \ if os.path.exists(vec_path):\n conn.enable_load_extension(True)\n\ \ conn.load_extension(vec_path)\n cur =\ \ conn.cursor()\n chk = cur.execute(\"SELECT count(*) FROM memory_index_chunks\"\ ).fetchone()[0]\n if chk > 0:\n print(f\"\ [dedup_subagents] Purging {chk} duplicated vector chunks from agent {agent}...\"\ )\n for t in [\n \"memory_index_sources\"\ ,\n \"memory_index_chunks\",\n \ \ \"memory_index_chunk_recall_metadata\",\n \"memory_index_chunk_provenance\"\ ,\n \"memory_entry_origins\",\n \ \ \"memory_embedding_cache\",\n \"memory_index_chunks_vec\"\ \n ]:\n try: cur.execute(f\"DELETE\ \ FROM {t}\")\n except Exception: pass\n \ \ conn.commit()\n cur.execute(\"VACUUM\")\n \ \ print(f\"[dedup_subagents] Reclaimed disk space for agent {agent}\"\ )\n conn.close()\n except Exception as e:\n \ \ print(f\"[dedup_subagents] Notice for {agent}: {e}\", file=sys.stderr)\n" replay_interrupted_turns.py: " #!/usr/bin/env python3\n import\ \ json, os, subprocess, sys, time, urllib.request\n\n INTERRUPTED_FILE\ \ = \"/tmp/openclaw-interrupted-turns.json\"\n\n if not os.path.exists(INTERRUPTED_FILE):\n\ \ sys.exit(0)\n\n try:\n with open(INTERRUPTED_FILE,\ \ \"r\") as f:\n turns = json.load(f)\n except Exception\ \ as e:\n print(f\"[replay_interrupted_turns] Failed to read {INTERRUPTED_FILE}:\ \ {e}\", file=sys.stderr)\n sys.exit(0)\n\n if not turns:\n\ \ sys.exit(0)\n\n print(f\"[replay_interrupted_turns] Found\ \ {len(turns)} interrupted turn(s). Waiting for gateway /healthz...\")\n\n \ \ gateway_ready = False\n for _ in range(60):\n try:\n\ \ with urllib.request.urlopen(\"http://127.0.0.1:18790/healthz\"\ , timeout=3) as resp:\n if resp.status == 200:\n \ \ gateway_ready = True\n break\n \ \ except Exception:\n pass\n time.sleep(2)\n\ \n if not gateway_ready:\n print(\"[replay_interrupted_turns]\ \ Gateway did not become healthy in 120s. Aborting.\", file=sys.stderr)\n \ \ sys.exit(1)\n\n # Allow 5s for Telegram ingress to bind\n\ \ time.sleep(5)\n\n for turn in turns:\n sk = turn.get(\"\ session_key\")\n sid = turn.get(\"session_id\")\n agent_id\ \ = turn.get(\"agent\")\n ctx = turn.get(\"context\") or {}\n \ \ prompt = turn.get(\"prompt\") or \"\"\n \n \ \ channel = ctx.get(\"channel\") or \"telegram\"\n to_target =\ \ ctx.get(\"to\") or \"\"\n thread_id = ctx.get(\"threadId\")\n \ \ \n target = to_target\n if target.startswith(\"\ telegram:\"):\n parts = target.split(\":\")\n \ \ target = parts[1] if len(parts) > 1 else target\n\n print(f\"\ [replay_interrupted_turns] Processing agent '{agent_id}' on session '{sk}' target='{target}'\ \ thread='{thread_id}'\")\n \n # 1. Send status notification\ \ to Telegram if target exists\n if target:\n notice\ \ = \"\u26A0\uFE0F OpenClaw wurde w\xE4hrend der Bearbeitung neu gestartet \u2013\ \ deine Anfrage wird jetzt nachgeholt...\"\n notify_cmd = [\n\ \ \"openclaw\", \"message\", \"send\",\n \ \ \"--channel\", channel,\n \"--target\", str(target),\n\ \ \"-m\", notice\n ]\n \ \ if thread_id is not None:\n notify_cmd.extend([\"--thread-id\"\ , str(thread_id)])\n try:\n subprocess.run(notify_cmd,\ \ check=False, timeout=30)\n print(f\"[replay_interrupted_turns]\ \ Sent recovery notice to {target}\")\n except Exception as e:\n\ \ print(f\"[replay_interrupted_turns] Notice failed: {e}\"\ , file=sys.stderr)\n\n # 2. Replay agent turn\n if sk\ \ and prompt:\n p_short = (prompt[:60] + \"...\") if len(prompt)\ \ > 60 else prompt\n print(f\"[replay_interrupted_turns] Replaying\ \ agent turn: {p_short}\")\n agent_cmd = [\n \ \ \"openclaw\", \"agent\",\n \"--session-key\", sk,\n\ \ \"-m\", prompt,\n \"--deliver\"\n\ \ ]\n try:\n res = subprocess.run(agent_cmd,\ \ capture_output=True, text=True, timeout=300)\n if res.returncode\ \ == 0:\n print(f\"[replay_interrupted_turns] Turn replay\ \ succeeded for {sk}\")\n else:\n \ \ print(f\"[replay_interrupted_turns] Turn replay exited with code {res.returncode}:\ \ {res.stderr[:200]}\", file=sys.stderr)\n except Exception as\ \ e:\n print(f\"[replay_interrupted_turns] Replay execution\ \ error: {e}\", file=sys.stderr)\n\n try:\n os.remove(INTERRUPTED_FILE)\n\ \ print(f\"[replay_interrupted_turns] Cleaned up {INTERRUPTED_FILE}\"\ )\n except Exception:\n pass\n" patch_garmin_mcp.py: "import os\n\np = \"/home/node/.openclaw/mcp-venv/lib/python3.12/site-packages/garmin_mcp/workouts.py\"\ \nif os.path.exists(p):\n with open(p, \"r\") as f:\n c = f.read()\n\ \ if \"_normalize_sport_type\" not in c:\n patch = '''\nSPORT_TYPE_MAPPINGS\ \ = {\n \"running\": {\"sportTypeId\": 1, \"sportTypeKey\": \"running\", \"\ displayOrder\": 1},\n \"cycling\": {\"sportTypeId\": 2, \"sportTypeKey\": \"\ cycling\", \"displayOrder\": 2},\n \"other\": {\"sportTypeId\": 3, \"sportTypeKey\"\ : \"other\", \"displayOrder\": 3},\n \"lap_swimming\": {\"sportTypeId\": 4,\ \ \"sportTypeKey\": \"lap_swimming\", \"displayOrder\": 4},\n \"swimming\"\ : {\"sportTypeId\": 4, \"sportTypeKey\": \"lap_swimming\", \"displayOrder\": 4},\n\ \ \"strength_training\": {\"sportTypeId\": 5, \"sportTypeKey\": \"strength_training\"\ , \"displayOrder\": 5},\n \"cardio_training\": {\"sportTypeId\": 6, \"sportTypeKey\"\ : \"cardio_training\", \"displayOrder\": 6},\n \"cardio\": {\"sportTypeId\"\ : 6, \"sportTypeKey\": \"cardio_training\", \"displayOrder\": 6},\n \"yoga\"\ : {\"sportTypeId\": 7, \"sportTypeKey\": \"yoga\", \"displayOrder\": 8},\n \ \ \"pilates\": {\"sportTypeId\": 8, \"sportTypeKey\": \"pilates\", \"displayOrder\"\ : 9},\n \"hiit\": {\"sportTypeId\": 9, \"sportTypeKey\": \"hiit\", \"displayOrder\"\ : 7},\n \"mobility\": {\"sportTypeId\": 11, \"sportTypeKey\": \"mobility\"\ , \"displayOrder\": 11},\n \"walking\": {\"sportTypeId\": 12, \"sportTypeKey\"\ : \"walking\", \"displayOrder\": 10},\n \"rucking\": {\"sportTypeId\": 13,\ \ \"sportTypeKey\": \"rucking\", \"displayOrder\": 12},\n}\nSPORT_ID_MAPPINGS\ \ = {v[\"sportTypeId\"]: v for v in SPORT_TYPE_MAPPINGS.values()}\n\ndef _resolve_sport_block(st):\n\ \ if isinstance(st, str):\n k = st.strip().lower()\n if k in\ \ SPORT_TYPE_MAPPINGS:\n return dict(SPORT_TYPE_MAPPINGS[k])\n elif\ \ isinstance(st, dict):\n key = st.get(\"sportTypeKey\")\n sid =\ \ st.get(\"sportTypeId\")\n if key and str(key).strip().lower() in SPORT_TYPE_MAPPINGS:\n\ \ base = dict(SPORT_TYPE_MAPPINGS[str(key).strip().lower()])\n \ \ if sid is not None and sid != 0 and sid != base[\"sportTypeId\"]:\n \ \ raise ValueError(f\"sportType mismatch: {key} vs {sid}\")\n \ \ return base\n if sid is not None and sid in SPORT_ID_MAPPINGS:\n\ \ return dict(SPORT_ID_MAPPINGS[sid])\n return None\n\ndef _normalize_sport_type(workout_data:\ \ dict) -> None:\n resolved = _resolve_sport_block(workout_data.get(\"sportType\"\ ))\n segments = workout_data.get(\"workoutSegments\") or []\n if not resolved\ \ and segments:\n for seg in segments:\n resolved = _resolve_sport_block(seg.get(\"\ sportType\"))\n if resolved:\n break\n if not resolved:\n\ \ raise ValueError(\"Missing or invalid sportType. Both sportTypeId and\ \ sportTypeKey are required.\")\n workout_data[\"sportType\"] = resolved\n\ \ for seg in segments:\n st = _resolve_sport_block(seg.get(\"sportType\"\ ))\n seg[\"sportType\"] = st if st else dict(resolved)\n\n'''\n \ \ t = \"def _normalize_workout_steps(workout_data: dict) -> None:\"\n if\ \ t in c:\n with open(p, \"w\") as f:\n f.write(c.replace(t,\ \ patch + t + \"\\n _normalize_sport_type(workout_data)\", 1))\n \ \ print(\"Successfully patched garmin_mcp workouts.py!\")\n" patch_perplexity.mjs: "import fs from \"node:fs\";\nimport path from \"node:path\"\ ;\nimport { globSync } from \"node:fs\";\n\nconst baseDir = \"/home/node/.openclaw/extensions/perplexity/dist\"\ ;\nif (fs.existsSync(baseDir)) {\n const files = fs.readdirSync(baseDir).filter(f\ \ => f.startsWith(\"perplexity-web-search-provider.runtime-\") && f.endsWith(\"\ .js\"));\n for (const filename of files) {\n const file = path.join(baseDir,\ \ filename);\n let code = fs.readFileSync(file, \"utf8\");\n if (!code.includes(\"\ /* PATCHED_CHAT_COMPLETIONS */\")) {\n const target = \"const structured\ \ = runtime.transport === \\\"search_api\\\";\";\n const fastPath = `const\ \ structured = runtime.transport === \"search_api\"; /* PATCHED_CHAT_COMPLETIONS\ \ */\n if (!structured) {\n const cacheKey = buildSearchCacheKey([\n\ \ \"perplexity\",\n runtime.transport,\n runtime.baseUrl,\n\ \ runtime.model,\n query,\n freshness\n \ \ ]);\n const cacheTtlMs = resolveSearchCacheTtlMs(searchConfig);\n\ \ const cached = readCachedSearchPayload(cacheKey, cacheTtlMs);\n \ \ if (cached) return cached;\n const start = Date.now();\n \ \ const timeoutSeconds = resolveSearchTimeoutSeconds(searchConfig);\n \ \ const result = await runPerplexitySearch({\n query,\n \ \ apiKey: runtime.apiKey,\n baseUrl: runtime.baseUrl,\n \ \ model: runtime.model,\n timeoutSeconds,\n signal,\n\ \ freshness\n });\n const payload = {\n \ \ query,\n provider: \"perplexity\",\n model: runtime.model,\n\ \ tookMs: Date.now() - start,\n externalContent: {\n \ \ untrusted: true,\n source: \"web_search\",\n \ \ provider: \"perplexity\",\n wrapped: true\n },\n\ \ content: wrapWebContent(result.content, \"web_search\"),\n \ \ citations: result.citations\n };\n writeCachedSearchPayload(cacheKey,\ \ payload);\n return payload;\n }`;\n if (code.includes(target))\ \ {\n code = code.replace(target, fastPath);\n fs.writeFileSync(file,\ \ code, \"utf8\");\n console.log(\"Patched perplexity runtime for chat\ \ completions:\", file);\n }\n }\n }\n}\n" file_cli.py: "#!/home/node/.openclaw/mcp-venv/bin/python3\nimport sys, os, mimetypes,\ \ filetype\n\ndef get_file_info(filepath, mime_only=False):\n if not os.path.exists(filepath):\n\ \ return \"cannot open (No such file or directory)\", \"cannot open (No\ \ such file or directory)\"\n if os.path.isdir(filepath):\n return \"\ directory\", \"inode/directory\"\n kind = filetype.guess(filepath)\n if\ \ kind is not None:\n return f\"{kind.extension.upper()} image/media data\ \ ({kind.mime})\", kind.mime\n guessed_mime, _ = mimetypes.guess_type(filepath)\n\ \ try:\n with open(filepath, \"rb\") as f:\n sample = f.read(4096)\n\ \ if len(sample) == 0:\n return \"empty\", \"application/x-empty\"\ \n try:\n sample.decode(\"utf-8\")\n if guessed_mime:\n\ \ return f\"{guessed_mime} text\", guessed_mime\n return\ \ \"ASCII text\", \"text/plain\"\n except UnicodeDecodeError:\n \ \ return \"data\", \"application/octet-stream\"\n except Exception as e:\n\ \ return f\"cannot read ({e})\", \"application/octet-stream\"\n\ndef main():\n\ \ args = sys.argv[1:]\n brief, mime_only, files = False, False, []\n \ \ for arg in args:\n if arg in (\"-b\", \"--brief\"):\n brief\ \ = True\n elif arg in (\"-i\", \"--mime\", \"--mime-type\"):\n \ \ mime_only = True\n elif arg.startswith(\"-\") and len(arg) > 1 and\ \ not arg.startswith(\"--\"):\n if \"b\" in arg: brief = True\n \ \ if \"i\" in arg: mime_only = True\n else:\n files.append(arg)\n\ \ if not files:\n sys.stderr.write(\"Usage: file [-b] [-i|--mime-type]\ \ ...\\n\")\n sys.exit(1)\n for f in files:\n desc, mime\ \ = get_file_info(f, mime_only)\n res = mime if mime_only else desc\n \ \ print(res if brief else f\"{f}: {res}\")\n\nif __name__ == \"__main__\"\ :\n main()\n" compact_sessions.py: "#!/usr/bin/env python3\n\"\"\"\nNightly multi-agent session\ \ maintenance and compaction script for OpenClaw.\n1. Pre-warms OS page cache\ \ for SQLite databases on spinning disks.\n2. Recovers any wedged or locked sessions\ \ via recover_sessions.py.\n3. Prunes abandoned isolated cron sessions (>48h old)\ \ to prevent token/state bloat.\n4. Tier A Hard-Reset: Resets koch, hebamme, and\ \ shopping topics to 0 tokens (clean archive).\n5. Tier B Smart-Compaction: Compacts\ \ active sessions exceeding token thresholds (5s pacing).\n6. Checkpoints SQLite\ \ WAL journals across all agent databases to disk.\n7. Optimizes SQLite query\ \ planner without blocking full VACUUM.\n\"\"\"\nimport glob\nimport json\nimport\ \ os\nimport sqlite3\nimport subprocess\nimport sys\nimport time\n\nTHRESHOLD_TOKENS\ \ = 15000\nTHRESHOLD_TRANSCRIPT_BYTES = 200000\nTHRESHOLD_EVENT_COUNT = 30\nCRON_MAX_AGE_HOURS\ \ = 48\nRPC_TIMEOUT_MS = 180000\n\n# Tracking Channels: Deterministic sliding-window\ \ (mila_tracker, nutrition_tracker).\n# Strict sliding window of ~5-8 events (2-3\ \ turns max), 0 LLM tokens, instant truncation.\nTRACKING_TARGETS = [\n (\"\ mila_tracker\", \"agent:mila_tracker:telegram:group:-1004326921510:topic:2796\"\ ),\n (\"nutrition_tracker\", \"agent:nutrition_tracker:telegram:group:-1004326921510:topic:2797\"\ ),\n]\nMAX_TRACKING_EVENTS = 8\nMAX_TRACKING_LINES = 8\n\n# Tier A: High-frequency\ \ interaction topics (koch, hebamme, shopping).\n# Deep-compaction keeps a compact\ \ summary + latest 2 turns without completely wiping conversational memory.\n\ TIER_A_TARGETS = [\n (\"koch\", \"agent:koch:telegram:group:-1004326921510:topic:21\"\ ),\n (\"hebamme\", \"agent:hebamme:telegram:group:-1004326921510:topic:22\"\ ),\n (\"shopping\", \"agent:shopping:telegram:group:-1004326921510:topic:115\"\ ),\n]\n\ndef prewarm_page_cache():\n print(\"=== Pre-warming SQLite databases\ \ into OS page cache ===\")\n t0 = time.time()\n try:\n subprocess.run(\n\ \ \"cat /home/node/.openclaw/state/openclaw.sqlite /home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\ \ > /dev/null 2>&1 || true\",\n shell=True,\n timeout=90.0\n\ \ )\n print(f\"Page cache pre-warmed in {time.time() - t0:.2f}s\"\ )\n except Exception as e:\n print(f\"Page cache pre-warming warning:\ \ {e}\")\n\ndef prune_stale_cron_sessions():\n print(f\"=== Pruning isolated\ \ cron sessions older than {CRON_MAX_AGE_HOURS}h ===\")\n cutoff_ms = int(time.time()\ \ * 1000) - (CRON_MAX_AGE_HOURS * 3600 * 1000)\n total_pruned = 0\n total_tokens_reclaimed\ \ = 0\n\n for db in sorted(glob.glob(\"/home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\"\ )):\n agent = db.split(\"/\")[5]\n try:\n conn = sqlite3.connect(db,\ \ timeout=30.0)\n conn.execute(\"PRAGMA foreign_keys = ON;\")\n \ \ cursor = conn.cursor()\n cursor.execute(\"SELECT name FROM\ \ sqlite_master WHERE type=\\\"table\\\"\")\n all_tables = set(r[0]\ \ for r in cursor.fetchall())\n\n cursor.execute(\"SELECT session_key,\ \ current_session_id, updated_at, entry_json FROM session_nodes WHERE session_key\ \ LIKE \\\"%:cron:%\\\"\")\n stale = []\n for k, sid, up,\ \ ej in cursor.fetchall():\n if up and up < cutoff_ms:\n \ \ tok = 0\n if ej:\n try:\n\ \ tok = json.loads(ej).get(\"totalTokens\", 0) or 0\n\ \ except Exception:\n pass\n\ \ stale.append((k, sid, tok))\n\n if stale:\n \ \ tokens_reclaimed = sum(t[2] for t in stale)\n for\ \ k, sid, _ in stale:\n for t in [\"trajectory_runtime_events\"\ , \"transcript_events\", \"transcript_event_identities\", \"session_transcript_active_events\"\ , \"transcript_rewrite_watermarks\", \"session_transcript_index_state\", \"session_transcript_fts\"\ , \"session_conversations\"]:\n if t in all_tables and\ \ sid:\n cursor.execute(f\"DELETE FROM {t} WHERE session_id\ \ = ?\", (sid,))\n for t in [\"session_participants\", \"heartbeat_outcomes\"\ , \"session_progress_cards\", \"board_widgets\", \"board_tabs\"]:\n \ \ if t in all_tables:\n cursor.execute(f\"\ DELETE FROM {t} WHERE session_key = ?\", (k,))\n if \"memory_entry_origins\"\ \ in all_tables:\n cursor.execute(\"DELETE FROM memory_entry_origins\ \ WHERE session_key = ? OR session_id = ?\", (k, sid or \"\"))\n \ \ if \"session_windows\" in all_tables:\n cursor.execute(\"\ DELETE FROM session_windows WHERE session_key = ? OR session_id = ?\", (k, sid\ \ or \"\"))\n cursor.execute(\"DELETE FROM session_nodes WHERE\ \ session_key = ?\", (k,))\n conn.commit()\n total_pruned\ \ += len(stale)\n total_tokens_reclaimed += tokens_reclaimed\n\ \ print(f\" [{agent}] Pruned {len(stale)} dead cron sessions ({tokens_reclaimed}\ \ tokens reclaimed)\")\n\n # Unconditionally resolve any dangling foreign\ \ key references across all tables\n while True:\n cursor.execute(\"\ PRAGMA foreign_key_check;\")\n violations = cursor.fetchall()\n\ \ if not violations:\n break\n \ \ for table, rowid, parent, fkid in violations:\n cursor.execute(f\"\ DELETE FROM {table} WHERE rowid = ?\", (rowid,))\n conn.commit()\n\ \ conn.close()\n except Exception as e:\n print(f\"\ \ [{agent}] Pruning error: {e}\")\n\n # Clear resolved quarantine entries\ \ if all databases are healthy\n try:\n q_path = \"/home/node/.openclaw/state/openclaw-quarantine.sqlite\"\ \n if os.path.exists(q_path):\n q_conn = sqlite3.connect(q_path)\n\ \ q_cur = q_conn.cursor()\n q_cur.execute(\"DELETE FROM\ \ quarantined_databases\")\n q_conn.commit()\n q_conn.close()\n\ \ except Exception as e:\n print(f\"Quarantine cleanup error: {e}\"\ )\n\n print(f\"Cron pruning complete: {total_pruned} sessions pruned, {total_tokens_reclaimed}\ \ tokens reclaimed.\")\n\ndef compact_tracking_sessions():\n print(\"=== Tracking\ \ Channels Sliding-Window Maintenance (mila_tracker, nutrition_tracker) ===\"\ )\n for agent, key in TRACKING_TARGETS:\n db_path = f\"/home/node/.openclaw/agents/{agent}/agent/openclaw-agent.sqlite\"\ \n if not os.path.exists(db_path):\n continue\n try:\n\ \ con = sqlite3.connect(db_path, timeout=5.0)\n row = con.execute(\"\ SELECT current_session_id FROM session_nodes WHERE session_key=?\", (key,)).fetchone()\n\ \ if not row or not row[0]:\n con.close()\n \ \ print(f\" [{agent}] No active session node found for {key}.\")\n \ \ continue\n sid = row[0]\n cnt = con.execute(\"\ SELECT count(*) FROM transcript_events WHERE session_id=?\", (sid,)).fetchone()[0]\n\ \ con.close()\n\n if cnt > MAX_TRACKING_EVENTS:\n \ \ print(f\" [{agent}] Session {key} has {cnt} events (> {MAX_TRACKING_EVENTS}).\ \ Truncating to last {MAX_TRACKING_LINES} lines...\")\n cmd = [\n\ \ \"openclaw\", \"sessions\", \"compact\", key,\n \ \ \"--agent\", agent,\n \"--max-lines\", str(MAX_TRACKING_LINES),\n\ \ \"--timeout\", \"30000\"\n ]\n \ \ r = subprocess.run(cmd, capture_output=True, text=True)\n \ \ if r.returncode == 0:\n print(f\" \u2713 Truncated successfully:\ \ {r.stdout.strip()}\")\n else:\n print(f\"\ \ \u2717 Truncation failed: {r.stderr.strip()}\")\n else:\n \ \ print(f\" [{agent}] Session {key} has {cnt} events (<= {MAX_TRACKING_EVENTS}).\ \ Clean.\")\n except Exception as e:\n print(f\" \u2717 [{agent}]\ \ Error during tracking compaction: {e}\")\n\ndef compact_tier_a_sessions():\n\ \ print(\"=== Tier A: Deep-Compaction (koch, hebamme, shopping) ===\")\n \ \ for agent, key in TIER_A_TARGETS:\n db_path = f\"/home/node/.openclaw/agents/{agent}/agent/openclaw-agent.sqlite\"\ \n if not os.path.exists(db_path):\n continue\n try:\n\ \ con = sqlite3.connect(db_path, timeout=10.0)\n row = con.execute(\"\ SELECT current_session_id FROM session_nodes WHERE session_key=?\", (key,)).fetchone()\n\ \ con.close()\n if not row or not row[0]:\n \ \ print(f\" [Tier A] {agent} ({key}) already at 0 (no active session).\")\n\ \ continue\n ev_cnt, ev_bytes = get_session_metrics(agent,\ \ key, row[0])\n if ev_cnt <= 2 and ev_bytes < 20000:\n \ \ print(f\" [Tier A] {agent} ({key}) has only {ev_cnt} events ({ev_bytes}\ \ bytes), skipping.\")\n continue\n print(f\" [Tier\ \ A] Deep-compacting {agent} ({key}) [{ev_cnt} events, {ev_bytes//1024}KB]...\"\ )\n r = subprocess.run(\n [\"openclaw\", \"sessions\"\ , \"compact\", key, \"--agent\", agent, \"--timeout\", str(RPC_TIMEOUT_MS)],\n\ \ capture_output=True, text=True\n )\n if\ \ r.returncode == 0:\n print(f\" \u2713 Deep-compacted: {r.stdout.strip()}\"\ )\n elif \"Already compacted\" in r.stderr or \"Already compacted\"\ \ in r.stdout:\n print(f\" \u2713 Already compacted.\")\n \ \ else:\n print(f\" \u2717 Standard compact error:\ \ {r.stderr.strip()}; retrying with --max-lines 40...\")\n fb =\ \ subprocess.run(\n [\"openclaw\", \"sessions\", \"compact\"\ , key, \"--agent\", agent, \"--max-lines\", \"40\", \"--timeout\", str(RPC_TIMEOUT_MS)],\n\ \ capture_output=True, text=True\n )\n \ \ if fb.returncode == 0:\n print(f\" \u2713 Truncation\ \ fallback success: {fb.stdout.strip()}\")\n else:\n \ \ print(f\" \u2717 Truncation fallback failed: {fb.stderr.strip()}\"\ )\n except Exception as e:\n print(f\" \u2717 Exception deep-compacting\ \ {agent}: {e}\")\n time.sleep(2)\n\ndef get_session_metrics(agent, key,\ \ sid=None):\n db_path = f\"/home/node/.openclaw/agents/{agent}/agent/openclaw-agent.sqlite\"\ \n if not os.path.exists(db_path):\n return 0, 0\n try:\n \ \ con = sqlite3.connect(db_path, timeout=5.0)\n cur = con.cursor()\n \ \ if not sid:\n row = cur.execute(\"SELECT current_session_id\ \ FROM session_nodes WHERE session_key=?\", (key,)).fetchone()\n sid\ \ = row[0] if row else None\n if not sid:\n con.close()\n \ \ return 0, 0\n ev_cnt, ev_bytes = cur.execute(\n \"\ SELECT count(*), coalesce(sum(length(event_json)), 0) FROM transcript_events WHERE\ \ session_id=?\", \n (sid,)\n ).fetchone()\n con.close()\n\ \ return ev_cnt, ev_bytes\n except Exception:\n return 0, 0\n\ \ndef compact_active_sessions():\n excluded_keys = set(t[1] for t in TIER_A_TARGETS)\ \ | set(t[1] for t in TRACKING_TARGETS)\n print(f\"=== Tier B: Running Multi-Agent\ \ Compaction (threshold: {THRESHOLD_TOKENS} tokens / {THRESHOLD_TRANSCRIPT_BYTES//1024}KB,\ \ LLM summarization) ===\")\n res = subprocess.run([\"openclaw\", \"sessions\"\ , \"--all-agents\", \"--json\"], capture_output=True, text=True)\n if res.returncode\ \ != 0:\n print(f\"Failed to list sessions: {res.stderr}\")\n return\n\ \n sessions = json.loads(res.stdout).get(\"sessions\", [])\n compacted =\ \ 0\n skipped = 0\n\n for s in sessions:\n key = s.get(\"key\", \"\ \")\n agent = s.get(\"agentId\") or \"main\"\n tokens = s.get(\"\ totalTokens\") or 0\n sid = s.get(\"sessionId\")\n\n # Skip Tier\ \ A and Tracking agents as they have dedicated routines\n if key in excluded_keys:\n\ \ continue\n\n # Target main sessions and all telegram sessions\ \ (topics and direct)\n is_target = (\n key == \"agent:main:main\"\ \ or \n \":telegram:\" in key or\n (key.startswith(\"agent:\"\ ) and key.endswith(\":main\"))\n )\n if is_target and not key.endswith(\"\ :heartbeat\"):\n ev_cnt, ev_bytes = get_session_metrics(agent, key,\ \ sid)\n needs_compaction = (\n tokens >= THRESHOLD_TOKENS\ \ or\n ev_bytes >= THRESHOLD_TRANSCRIPT_BYTES or\n \ \ (tokens == 0 and ev_cnt >= THRESHOLD_EVENT_COUNT)\n )\n \ \ if needs_compaction:\n stat_str = f\"{tokens} tokens\" if\ \ tokens else f\"{ev_bytes//1024}KB / {ev_cnt} events\"\n print(f\"\ Compacting {agent} ({stat_str}) -> {key}...\")\n c_res = subprocess.run(\n\ \ [\"openclaw\", \"sessions\", \"compact\", key, \"--agent\"\ , agent, \"--timeout\", str(RPC_TIMEOUT_MS)],\n capture_output=True,\ \ text=True\n )\n if c_res.returncode == 0:\n \ \ print(f\" \u2713 {c_res.stdout.strip()}\")\n \ \ compacted += 1\n elif \"Already compacted\" in c_res.stderr\ \ or \"Already compacted\" in c_res.stdout:\n print(f\" \u2713\ \ Already compacted.\")\n skipped += 1\n else:\n\ \ print(f\" \u2717 {c_res.stderr.strip()}; retrying with --max-lines\ \ 80 fallback...\")\n fb_res = subprocess.run(\n \ \ [\"openclaw\", \"sessions\", \"compact\", key, \"--agent\", agent,\ \ \"--max-lines\", \"80\", \"--timeout\", str(RPC_TIMEOUT_MS)],\n \ \ capture_output=True, text=True\n )\n \ \ if fb_res.returncode == 0:\n print(f\" \u2713\ \ (fallback) {fb_res.stdout.strip()}\")\n compacted +=\ \ 1\n else:\n print(f\" \u2717 Fallback\ \ failed: {fb_res.stderr.strip()}\")\n time.sleep(5) # 5s pause\ \ to avoid rotational HDD I/O saturation\n else:\n skipped\ \ += 1\n\n print(f\"Compaction complete: {compacted} compacted, {skipped} skipped.\"\ )\n\ndef checkpoint_wal_journals():\n print(\"=== Checkpointing SQLite WAL\ \ journals across all agent databases ===\")\n dbs = [\"/home/node/.openclaw/state/openclaw.sqlite\"\ ] + glob.glob(\"/home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\")\n\ \ wal_freed = 0\n for db in dbs:\n wal = db + \"-wal\"\n if\ \ os.path.exists(wal):\n sz = os.path.getsize(wal)\n try:\n\ \ con = sqlite3.connect(db, timeout=30.0)\n con.execute(\"\ PRAGMA wal_checkpoint(TRUNCATE);\")\n con.close()\n \ \ wal_freed += sz\n except Exception as e:\n print(f\"\ \ \u2717 Error checkpointing {db}: {e}\")\n print(f\"WAL Checkpoint complete:\ \ {wal_freed / (1024*1024):.2f} MB merged to disk.\")\n\ndef vacuum_and_optimize():\n\ \ print(\"=== Running SQLite OPTIMIZE across all databases ===\")\n dbs\ \ = [\"/home/node/.openclaw/state/openclaw.sqlite\"] + glob.glob(\"/home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite\"\ )\n for db in dbs:\n try:\n con = sqlite3.connect(db, timeout=30.0)\n\ \ con.execute(\"PRAGMA optimize;\")\n con.close()\n \ \ except Exception as e:\n print(f\" \u2717 Error optimizing {db}:\ \ {e}\")\n print(\"SQLite optimization complete.\")\n\ndef main():\n prewarm_page_cache()\n\ \ try:\n subprocess.run([\"python3\", \"/home/node/.openclaw/scripts/recover_sessions.py\"\ ], check=False)\n except Exception:\n pass\n prune_stale_cron_sessions()\n\ \ compact_tracking_sessions()\n compact_tier_a_sessions()\n compact_active_sessions()\n\ \ checkpoint_wal_journals()\n vacuum_and_optimize()\n\nif __name__ == \"\ __main__\":\n main()\n" compact_tracking_sessions.py: "#!/usr/bin/env python3\n\"\"\"\nLightweight sliding-window\ \ compaction for OpenClaw tracking channels:\n- mila_tracker (topic 2796)\n- nutrition_tracker\ \ (topic 2797)\n\nMaintains a strict sliding window of ~5-8 transcript events\ \ (max 2-3 turns).\nUses native gateway truncation (--max-lines 8) requiring 0\ \ LLM tokens and 0 external API calls.\n\"\"\"\nimport os\nimport sqlite3\nimport\ \ subprocess\nimport sys\nimport time\n\nMAX_TRACKING_EVENTS = 8\nMAX_TRACKING_LINES\ \ = 8\nTIMEOUT_MS = 30000\n\nTRACKING_TARGETS = [\n (\"mila_tracker\", \"agent:mila_tracker:telegram:group:-1004326921510:topic:2796\"\ ),\n (\"nutrition_tracker\", \"agent:nutrition_tracker:telegram:group:-1004326921510:topic:2797\"\ ),\n]\n\ndef check_and_compact_tracking():\n compacted_count = 0\n skipped_count\ \ = 0\n\n for agent, key in TRACKING_TARGETS:\n db_path = f\"/home/node/.openclaw/agents/{agent}/agent/openclaw-agent.sqlite\"\ \n if not os.path.exists(db_path):\n continue\n try:\n\ \ con = sqlite3.connect(db_path, timeout=5.0)\n row = con.execute(\"\ SELECT current_session_id FROM session_nodes WHERE session_key=?\", (key,)).fetchone()\n\ \ if not row or not row[0]:\n con.close()\n \ \ continue\n sid = row[0]\n cnt = con.execute(\"\ SELECT count(*) FROM transcript_events WHERE session_id=?\", (sid,)).fetchone()[0]\n\ \ con.close()\n\n if cnt > MAX_TRACKING_EVENTS:\n \ \ print(f\"[{agent}] Session {key} has {cnt} events (threshold {MAX_TRACKING_EVENTS}).\ \ Truncating to last {MAX_TRACKING_LINES} lines...\")\n cmd = [\n\ \ \"openclaw\", \"sessions\", \"compact\", key,\n \ \ \"--agent\", agent,\n \"--max-lines\", str(MAX_TRACKING_LINES),\n\ \ \"--timeout\", str(TIMEOUT_MS)\n ]\n \ \ res = subprocess.run(cmd, capture_output=True, text=True)\n \ \ if res.returncode == 0:\n print(f\" \u2713 [{agent}]\ \ Truncated successfully: {res.stdout.strip()}\")\n compacted_count\ \ += 1\n else:\n print(f\" \u2717 [{agent}]\ \ Truncation failed: {res.stderr.strip()}\", file=sys.stderr)\n else:\n\ \ skipped_count += 1\n except Exception as e:\n \ \ print(f\"[{agent}] Error checking session: {e}\", file=sys.stderr)\n\n \ \ if compacted_count > 0:\n print(f\"Tracking maintenance: {compacted_count}\ \ sessions truncated, {skipped_count} within window.\")\n\nif __name__ == \"__main__\"\ :\n check_and_compact_tracking()\n" sync_crons.py: "#!/usr/bin/env python3\n\"\"\"\nsync_crons.py\nIdempotently reconciles\ \ declarative OpenClaw automations defined in crons.yaml\nwith the running OpenClaw\ \ gateway state.\n\nCan run directly inside the container or remotely via kubectl\ \ exec.\n\"\"\"\n\nimport json\nimport os\nimport subprocess\nimport sys\nimport\ \ yaml\n\ndef find_crons_file():\n candidates = [\n os.environ.get(\"\ CRONS_FILE\"),\n \"crons.yaml\",\n \"k8s/openclaw/crons.yaml\",\n\ \ \"/home/node/.openclaw/scripts/crons.yaml\",\n os.path.join(os.path.dirname(__file__),\ \ \"../crons.yaml\"),\n os.path.join(os.path.dirname(__file__), \"crons.yaml\"\ ),\n ]\n for c in candidates:\n if c and os.path.exists(c):\n \ \ return os.path.abspath(c)\n raise FileNotFoundError(\"Could not find\ \ crons.yaml. Pass via CRONS_FILE environment variable.\")\n\ndef is_inside_container():\n\ \ return os.path.exists(\"/home/node/.openclaw\") or os.environ.get(\"OPENCLAW_HOME\"\ ) is not None\n\ndef run_openclaw_cmd(args):\n if is_inside_container():\n\ \ cmd = [\"openclaw\"] + args\n else:\n namespace = os.environ.get(\"\ NAMESPACE\", \"openclaw\")\n pod = os.environ.get(\"POD_NAME\", \"openclaw-0\"\ )\n container = os.environ.get(\"CONTAINER_NAME\", \"openclaw\")\n \ \ cmd = [\"kubectl\", \"exec\", \"-n\", namespace, pod, \"-c\", container,\ \ \"--\", \"openclaw\"] + args\n \n res = subprocess.run(cmd, capture_output=True,\ \ text=True)\n if res.returncode != 0:\n raise RuntimeError(f\"OpenClaw\ \ CLI command failed ({' '.join(cmd)}):\\n{res.stderr}\\n{res.stdout}\")\n \ \ return res.stdout\n\ndef list_existing_jobs():\n raw = run_openclaw_cmd([\"\ cron\", \"list\", \"--json\"])\n try:\n data = json.loads(raw)\n \ \ return data.get(\"jobs\", [])\n except Exception as e:\n print(f\"\ Warning: could not parse cron list json: {e}\\nRaw output: {raw[:300]}\", file=sys.stderr)\n\ \ return []\n\ndef reconcile_jobs():\n file_path = find_crons_file()\n\ \ print(f\"=== Syncing OpenClaw Crons-as-Code from {file_path} ===\")\n \ \ \n with open(file_path, \"r\", encoding=\"utf-8\") as f:\n spec =\ \ yaml.safe_load(f)\n\n declarations = spec.get(\"jobs\", [])\n existing_jobs\ \ = list_existing_jobs()\n \n print(f\"Found {len(declarations)} declared\ \ jobs in YAML, {len(existing_jobs)} active jobs on gateway.\")\n\n for job\ \ in declarations:\n name = job.get(\"name\")\n decl_key = job.get(\"\ declarationKey\") or name\n desc = job.get(\"description\", \"\")\n \ \ sched = job.get(\"schedule\", {})\n \n # Match existing job\ \ by declarationKey or exact name\n match = next((j for j in existing_jobs\ \ if j.get(\"declarationKey\") == decl_key or j.get(\"name\") == name), None)\n\ \ \n if match:\n existing_id = match.get(\"id\")\n \ \ print(f\"Removing existing job '{name}' (ID {existing_id}) for idempotent\ \ recreation...\")\n try:\n run_openclaw_cmd([\"cron\"\ , \"rm\", existing_id])\n except Exception as e:\n print(f\"\ Warning removing {existing_id}: {e}\")\n\n # Build add command\n \ \ cmd = [\n \"cron\", \"add\",\n \"--name\", name,\n \ \ \"--declaration-key\", decl_key,\n \"--description\", desc,\n\ \ ]\n\n if sched.get(\"kind\") == \"every\":\n cmd.extend([\"\ --every\", sched.get(\"every\", \"15m\")])\n elif sched.get(\"kind\") ==\ \ \"cron\":\n cmd.extend([\"--cron\", sched.get(\"expr\", \"* * * *\ \ *\")])\n if sched.get(\"tz\"):\n cmd.extend([\"--tz\"\ , sched.get(\"tz\")])\n\n if job.get(\"session\"):\n cmd.extend([\"\ --session\", job.get(\"session\")])\n\n if job.get(\"delivery\") == \"\ none\":\n cmd.append(\"--no-deliver\")\n\n if job.get(\"command\"\ ):\n cmd.extend([\"--command\", job.get(\"command\")])\n\n if\ \ job.get(\"agent\"):\n cmd.extend([\"--agent\", job.get(\"agent\"\ )])\n\n if job.get(\"model\"):\n cmd.extend([\"--model\", job.get(\"\ model\")])\n\n if job.get(\"channel\"):\n cmd.extend([\"--channel\"\ , job.get(\"channel\")])\n\n if job.get(\"to\"):\n cmd.extend([\"\ --to\", job.get(\"to\")])\n\n if job.get(\"threadId\"):\n cmd.extend([\"\ --thread-id\", str(job.get(\"threadId\"))])\n\n if job.get(\"announce\"\ ):\n cmd.append(\"--announce\")\n\n if job.get(\"message\"):\n\ \ cmd.extend([\"--message\", job.get(\"message\")])\n\n print(f\"\ Registering job '{name}'...\")\n run_openclaw_cmd(cmd)\n print(f\"\ Successfully registered '{name}'.\")\n\n print(\"=== OpenClaw Crons-as-Code\ \ reconciliation complete! ===\")\n\nif __name__ == \"__main__\":\n reconcile_jobs()\n" triage.py: "#!/usr/bin/env python3\n\"\"\"\nopenclaw-triage.py: Rapid zero-dependency\ \ incident triage for OpenClaw multi-agent sessions.\nDirectly inspects ground-truth\ \ SQLite session state and transcript events in < 1 second.\n\"\"\"\nimport argparse\n\ import glob\nimport json\nimport os\nimport sqlite3\nimport sys\nimport time\n\ \nTOPIC_TO_AGENT = {\n \"20\": \"fitness\",\n \"21\": \"koch\",\n \"\ 22\": \"hebamme\",\n \"51\": \"travel\",\n \"115\": \"shopping\",\n \"\ 116\": \"regensburg\",\n \"117\": \"recherche\",\n \"118\": \"doktor\",\n\ \ \"119\": \"itguy\",\n \"164\": \"grabengasse_media\",\n \"2796\": \"\ mila_tracker\",\n \"2797\": \"nutrition_tracker\",\n}\n\ndef parse_args():\n\ \ parser = argparse.ArgumentParser(description=\"Rapid OpenClaw multi-agent\ \ session triage.\")\n parser.add_argument(\"--topic\", \"-t\", help=\"Telegram\ \ forum topic ID (e.g. 21, 22, 117)\")\n parser.add_argument(\"--agent\", \"\ -a\", help=\"Agent identifier (e.g. koch, hebamme, recherche)\")\n parser.add_argument(\"\ --limit\", \"-n\", type=int, default=10, help=\"Number of recent events to display\ \ (default: 10)\")\n parser.add_argument(\"--db-root\", default=\"/home/node/.openclaw/agents\"\ , help=\"Root path of agent SQLite databases\")\n return parser.parse_args()\n\ \ndef triage_agent_session(agent, topic_id=None, limit=10, db_root=\"/home/node/.openclaw/agents\"\ ):\n db_path = os.path.join(db_root, agent, \"agent\", \"openclaw-agent.sqlite\"\ )\n if not os.path.exists(db_path):\n print(f\"[-] Database not found\ \ for agent '{agent}': {db_path}\")\n return False\n\n try:\n \ \ con = sqlite3.connect(db_path, timeout=5.0)\n except Exception as e:\n \ \ print(f\"[-] Failed to connect to SQLite DB for agent '{agent}': {e}\"\ )\n return False\n\n cur = con.cursor()\n # Find matching session\n\ \ if topic_id:\n target_pattern = f\"%topic:{topic_id}%\"\n cur.execute(\n\ \ \"SELECT session_key, current_session_id, status, updated_at, entry_json\ \ FROM session_nodes WHERE session_key LIKE ? AND session_key NOT LIKE '%:heartbeat'\ \ ORDER BY updated_at DESC LIMIT 1\",\n (target_pattern,)\n \ \ )\n else:\n cur.execute(\n \"SELECT session_key, current_session_id,\ \ status, updated_at, entry_json FROM session_nodes WHERE session_key NOT LIKE\ \ '%:heartbeat' ORDER BY updated_at DESC LIMIT 1\"\n )\n\n row = cur.fetchone()\n\ \ if not row:\n print(f\"[-] No session found in {agent} DB (filter:\ \ topic={topic_id})\")\n con.close()\n return False\n\n skey,\ \ sid, status, updated_at_ms, entry_json = row\n now_ms = time.time() * 1000\n\ \ age_min = (now_ms - updated_at_ms) / 60000.0 if updated_at_ms else 0.0\n\n\ \ entry = {}\n if entry_json:\n try:\n entry = json.loads(entry_json)\n\ \ except Exception:\n pass\n\n tokens = entry.get(\"totalTokens\"\ , \"?\")\n model = entry.get(\"model\", \"default\")\n last_run_id = entry.get(\"\ activeWriterRunId\") or entry.get(\"activeRunId\", None)\n\n # Detect anomalies\n\ \ badges = []\n if isinstance(tokens, int):\n if tokens > 60000:\n\ \ badges.append(\"\U0001F6A8 [CRITICAL_CONTEXT_BLOAT: >60k tokens]\"\ )\n elif tokens > 20000:\n badges.append(\"\u26A0\uFE0F [HIGH_CONTEXT_TOKENS:\ \ >20k tokens]\")\n if status == \"running\" and age_min > 5.0:\n badges.append(\"\ \U0001F512 [POSSIBLY_WEDGED_RUNNING: updated >5m ago]\")\n elif status == \"\ processing\":\n badges.append(\"\u26A1 [ACTIVE_PROCESSING]\")\n\n #\ \ Check ingress queue status\n state_db = \"/home/node/.openclaw/state/openclaw.sqlite\"\ \n if topic_id and os.path.exists(state_db):\n try:\n s_con\ \ = sqlite3.connect(state_db, timeout=2.0)\n lane_pat = f\"%topic:{topic_id}%\"\ \n pending_cnt = s_con.execute(\n \"SELECT count(*)\ \ FROM channel_ingress_events WHERE lane_key LIKE ? AND status IN ('pending',\ \ 'claimed')\",\n (lane_pat,)\n ).fetchone()[0]\n \ \ s_con.close()\n if pending_cnt > 0:\n badges.append(f\"\ \U0001F4EC [INGRESS_STALLED: {pending_cnt} message(s) pending]\")\n except\ \ Exception:\n pass\n\n print(\"=\" * 72)\n print(f\"\U0001F3AF\ \ AGENT: {agent.upper():<12} | TOPIC: {topic_id or 'N/A':<6} | STATUS: {status.upper()}\"\ )\n print(f\" SESSION KEY: {skey}\")\n print(f\" SESSION ID: {sid}\"\ )\n print(f\" MODEL: {model}\")\n print(f\" TOKENS: {tokens}\ \ | LAST UPDATED: {age_min:.1f}m ago\")\n if badges:\n print(f\" ANOMALIES:\ \ {' '.join(badges)}\")\n print(\"=\" * 72)\n\n # Query transcript events\n\ \ cur.execute(\n \"SELECT seq, created_at, event_json FROM transcript_events\ \ WHERE session_id = ? ORDER BY seq DESC LIMIT ?\",\n (sid, limit)\n \ \ )\n events = cur.fetchall()\n con.close()\n\n if not events:\n \ \ print(\" (No transcript events found)\")\n return True\n\n events.reverse()\n\ \ print(\"\\n\U0001F4DC RECENT TRANSCRIPT EVENTS (Chronological):\")\n \n\ \ last_role = None\n for seq, ca_ms, ej_str in events:\n try:\n \ \ ev = json.loads(ej_str)\n except Exception:\n print(f\"\ [{seq}] \")\n continue\n\n etype = ev.get(\"\ type\")\n if etype != \"message\":\n continue\n\n msg\ \ = ev.get(\"message\", {})\n role = msg.get(\"role\")\n last_role\ \ = role\n content = msg.get(\"content\", [])\n\n if role == \"\ user\":\n txt = content if isinstance(content, str) else json.dumps(content)\n\ \ # Check for audio transcript\n if isinstance(content,\ \ str) and \"[Audio]\" in content:\n print(f\"\\n\U0001F449 [SEQ\ \ {seq}] \U0001F3A4 USER AUDIO PROMPT:\")\n for line in content.splitlines()[:6]:\n\ \ print(f\" {line}\")\n if len(content.splitlines())\ \ > 6:\n print(\" ...\")\n else:\n \ \ txt_preview = txt.replace(\"\\n\", \" \")[:240]\n print(f\"\ \\n\U0001F449 [SEQ {seq}] \U0001F464 USER: {txt_preview}\")\n\n elif role\ \ == \"assistant\":\n print(f\"\\n\U0001F916 [SEQ {seq}] ASSISTANT\ \ TURN:\")\n if isinstance(content, list):\n for item\ \ in content:\n if not isinstance(item, dict):\n \ \ continue\n itype = item.get(\"type\")\n \ \ if itype == \"thinking\":\n th = item.get(\"\ thinking\", \"\").replace(\"\\n\", \" \")\n print(f\" \ \ \U0001F9E0 THINKING: {th[:280]}...\")\n elif itype == \"\ toolCall\":\n tname = item.get(\"name\", \"unknown\")\n\ \ targs = json.dumps(item.get(\"arguments\", {}))\n \ \ print(f\" \u2699\uFE0F TOOL CALL: {tname}({targs[:160]})\"\ )\n elif itype == \"text\":\n tx = item.get(\"\ text\", \"\")\n lines = tx.splitlines()\n \ \ prev = lines[0][:150] if lines else \"\"\n \ \ print(f\" \U0001F4AC TEXT OUTPUT: {prev}...\")\n elif isinstance(content,\ \ str):\n print(f\" \U0001F4AC TEXT OUTPUT: {content[:200]}...\"\ )\n\n elif role == \"toolResult\":\n is_err = ev.get(\"isError\"\ , False)\n tname = ev.get(\"details\", {}).get(\"tool\") or msg.get(\"\ toolName\", \"tool\")\n res_txt = json.dumps(content) if not isinstance(content,\ \ str) else content\n if is_err or \"error\" in res_txt.lower() or\ \ \"must have required property\" in res_txt:\n print(f\" \u274C\ \ [SEQ {seq}] TOOL ERROR ({tname}): {res_txt[:260]}\")\n else:\n \ \ print(f\" \u2705 [SEQ {seq}] TOOL RESULT ({tname}): {res_txt[:180]}\"\ )\n\n if last_role == \"user\":\n print(\"\\n\u26A0\uFE0F WARNING: The\ \ last event in the transcript is an UNANSWERED USER MESSAGE!\")\n print(\"\ \ The agent turn either crashed, timed out, or was killed before producing a\ \ response.\")\n\n print(\"\\n\" + \"=\" * 72)\n return True\n\ndef main():\n\ \ args = parse_args()\n if not args.topic and not args.agent:\n print(\"\ Usage: openclaw-triage.py (--topic | --agent ) [--limit ]\")\n \ \ sys.exit(1)\n\n agent = args.agent\n topic = args.topic\n\n if\ \ topic and not agent:\n agent = TOPIC_TO_AGENT.get(str(topic))\n \ \ if not agent:\n # Search all DBs\n for a, t in TOPIC_TO_AGENT.items():\n\ \ if t == topic:\n agent = a\n \ \ break\n\n if not agent:\n # Scan all DBs for the topic\n \ \ for db in sorted(glob.glob(os.path.join(args.db_root, \"*/agent/openclaw-agent.sqlite\"\ ))):\n cand_agent = db.split(os.sep)[-3]\n try:\n \ \ con = sqlite3.connect(db, timeout=2.0)\n cur = con.cursor()\n\ \ cur.execute(\n \"SELECT session_key FROM session_nodes\ \ WHERE session_key LIKE ? LIMIT 1\",\n (f\"%topic:{topic}%\"\ ,)\n )\n if cur.fetchone():\n \ \ agent = cand_agent\n con.close()\n break\n\ \ con.close()\n except Exception:\n pass\n\ \n if not agent:\n print(f\"[-] Could not map topic '{topic}' to any\ \ known OpenClaw agent.\")\n sys.exit(1)\n\n success = triage_agent_session(agent,\ \ topic_id=topic, limit=args.limit, db_root=args.db_root)\n sys.exit(0 if success\ \ else 1)\n\nif __name__ == \"__main__\":\n main()\n" crons.yaml: "# Declarative Crons-as-Code for OpenClaw (Haumdaucher)\n# Reconciled\ \ idempotently via k8s/openclaw/scripts/sync_crons.py\n\nversion: 1\njobs:\n \ \ # 1. Maintenance & State Truncation\n - name: \"tracking-session-sliding-window\"\ \n declarationKey: \"tracking-session-sliding-window\"\n description: \"\ Lightweight sliding-window truncation (max 8 events) for mila_tracker and nutrition_tracker\"\ \n schedule:\n kind: \"every\"\n every: \"15m\"\n session: \"\ isolated\"\n delivery: \"none\"\n command: \"python3 /home/node/.openclaw/scripts/compact_tracking_sessions.py\"\ \n\n - name: \"nightly-session-compaction\"\n declarationKey: \"nightly-session-compaction\"\ \n description: \"Nightly multi-agent session compaction and SQLite WAL checkpointing\"\ \n schedule:\n kind: \"cron\"\n expr: \"45 4 * * *\"\n tz: \"\ Europe/Berlin\"\n session: \"isolated\"\n delivery: \"none\"\n command:\ \ \"python3 /home/node/.openclaw/scripts/compact_sessions.py\"\n\n # 2. Family\ \ Dashboards & Web Analytics\n - name: \"dashboards-periodic-publish\"\n declarationKey:\ \ \"dashboards-periodic-publish\"\n description: \"Periodically regenerates\ \ and uploads Mila & Moritz family HTML dashboards to S3\"\n schedule:\n \ \ kind: \"every\"\n every: \"10m\"\n session: \"isolated\"\n delivery:\ \ \"none\"\n command: \"sh -lc 'export PATH=/home/node/.openclaw/bin:$PATH;\ \ cd /home/node/mop-skills/mop-skills/skills/datastore && uv run scripts/update_website.py\ \ --data-dir /home/node/.openclaw/wiki/main/data && uv run scripts/update_moritz_website.py\ \ --data-dir /home/node/.openclaw/wiki/main/data'\"\n\n # 3. Nightly Garmin Connect\ \ Mirror\n - name: \"garmin-nightly-mirror\"\n declarationKey: \"garmin-nightly-mirror\"\ \n description: \"N\xE4chtlicher deterministischer Spiegel der Garmin Connect\ \ Daten (Aktivit\xE4ten, Daily Summary, Schlaf) in Git JSONL\"\n schedule:\n\ \ kind: \"cron\"\n expr: \"30 3 * * *\"\n tz: \"Europe/Berlin\"\ \n agent: \"fitness\"\n model: \"openrouter/deepseek/deepseek-v4.1-flash\"\ \n session: \"isolated\"\n channel: \"telegram\"\n to: \"-1004326921510\"\ \n threadId: \"20\"\n announce: true\n message: |\n F\xFChre den\ \ n\xE4chtlichen Garmin Connect Sync durch.\n Vorgehen:\n 1. F\xFChre\ \ den Garmin-Sync-Befehl aus:\n uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/garmin_sync.py\ \ --days 2 --data-dir /home/node/.openclaw/wiki/main/data\n 2. Aktualisiere\ \ die gerenderten Markdown-Ansichten:\n PYTHONPATH=/home/node/mop-skills/mop-skills/skills/datastore/scripts\ \ uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/render_views.py\ \ --data-dir /home/node/.openclaw/wiki/main/data --wiki-dir /home/node/.openclaw/wiki/main\n\ \ 3. Aktualisiere das pers\xF6nliche Health & Nutrition Dashboard (moritz.html):\n\ \ uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/update_moritz_website.py\ \ --data-dir /home/node/.openclaw/wiki/main/data\n 4. Git Commit & Push im\ \ Knowledge Wiki:\n cd /home/node/.openclaw/wiki/main && git add data/garmin/\ \ entities/nutrition-log/ public/ && git diff --staged --quiet || (git commit\ \ -m \"chore(garmin): automated nightly mirror [skip ci]\" && git push origin\ \ main)\n 5. Finale Antwort:\n - Bei Erfolg: Gib exakt 'NO_REPLY'\ \ aus (intentional silence, kein Telegram-Spam in der Nacht).\n - Nur\ \ bei Authentifizierungsfehlern: Informiere kurz via Telegram (Thema #Sport),\ \ dass ein Garmin-Re-Auth n\xF6tig ist, oder starte den Re-Auth-Flow.\n\n # 4.\ \ Weekly Autonomous System & Error Audit\n - name: \"itguy-weekly-health-audit\"\ \n declarationKey: \"itguy-weekly-health-audit\"\n description: \"W\xF6\ chentlicher Self-Audit der OpenClaw-Logs, Tool-Failures und Session-Transkripte\ \ mit Git-Commit-Korrelation\"\n schedule:\n kind: \"cron\"\n expr:\ \ \"0 3 * * 0\"\n tz: \"Europe/Berlin\"\n agent: \"itguy\"\n model:\ \ \"openrouter/deepseek/deepseek-v4.1-flash\"\n session: \"isolated\"\n \ \ channel: \"telegram\"\n to: \"-1004326921510\"\n threadId: \"119\"\n \ \ announce: true\n message: |\n F\xFChre den w\xF6chentlichen OpenClaw\ \ System- und Log-Audit durch.\n Vorgehen:\n 1. F\xFChre den Audit-Befehl\ \ aus:\n uv run /home/node/mop-skills/mop-skills/skills/openclaw-audit/scripts/audit_openclaw.py\ \ --since 7d --write-report --add-task --git-push\n 2. Analysiere die Ausgabe\ \ und korreliere eventuelle Fehler-Cluster mit den j\xFCngsten Commits in infrapuzzle,\ \ mop-skills und knowledge-wiki.\n 3. Finale Antwort:\n - Wenn NEUE\ \ Probleme gefunden oder Tasks in tasks/antigravity.md eingetragen wurden: Fasse\ \ die Ergebnisse kurz, pr\xE4zise und l\xF6sungsorientiert zusammen (betroffene\ \ Tools, vermutete Ursache, Link zur Task-Queue) und gib diese Zusammenfassung\ \ als finale Antwort aus.\n - Wenn das System gesund ist und KEINE neuen\ \ Tasks erzeugt wurden: Gib als finale Antwort exakt 'NO_REPLY' aus (intentional\ \ silence, kein Telegram-Spam).\n\n # 5. Weekly AI Roundup (Recherche)\n - name:\ \ \"weekly-ai-roundup\"\n declarationKey: \"weekly-ai-roundup:recherche\"\n\ \ description: \"W\xF6chentlicher AI-Roundup und Tech-Scanning f\xFCr Topic\ \ #Research\"\n schedule:\n kind: \"cron\"\n expr: \"30 6 * * 5\"\ \n tz: \"Europe/Berlin\"\n agent: \"recherche\"\n model: \"openrouter/deepseek/deepseek-v4.1-flash\"\ \n session: \"isolated\"\n channel: \"telegram\"\n to: \"-1004326921510\"\ \n threadId: \"117\"\n announce: true\n message: |\n \U0001F5D3\uFE0F\ \ W\xD6CHENTLICHER AI-ROUNDUP (Freitag). F\xFChre den in sources.md im Workspace\ \ definierten Ablauf aus und poste das Ergebnis als EINE kompakte Telegram-Nachricht\ \ in diesen Topic (#Research).\n\n 1) Hacker News: Algolia API \u2013 https://hn.algolia.com/api/v1/search_by_date?tags=story&numericFilters=points>150,created_at_i>{Unix-Timestamp\ \ von vor 7 Tagen}&hitsPerPage=100 \u2192 daraus die KI-relevanten Top-Stories\ \ nach Punkten ziehen.\n 2) Frontier-Labs pr\xFCfen: OpenAI, Anthropic, Google\ \ DeepMind, Meta AI, Mistral, xAI.\n 3) China-Labs pr\xFCfen: GLM/Z.ai, Qwen,\ \ DeepSeek, Moonshot/Kimi, MiniMax.\n 4) Tracker: Artificial Analysis Releases,\ \ Hugging Face Trending, OpenRouter Rankings, LMArena.\n 5) Newsletter/Blogs:\ \ The Batch, Import AI, Interconnects, Ahead of AI, Simon Willison, TLDR AI.\n\ \ 6) GitHub: Trending (weekly) + neue Releases der Kern-Orgs (openai, anthropics,\ \ google-deepmind, QwenLM, deepseek-ai, zai-org, meta-llama, mistralai).\n \ \ 7) YouTube nur als Hype-Indikator (Titel/Snippets), KEINE Transkripte.\n\n\ \ Ausgabe (Deutsch, Markdown, KEINE Tabellen):\n - Top-3-Highlights\ \ der Woche (je 1 Satz + Link)\n - Danach Bullets je Kategorie: \u2460 Frontier-Modelle\ \ \xB7 \u2461 China-Modelle \xB7 \u2462 Tools/Infra/Procedures \xB7 \u2463 Trends\ \ (HN/GitHub/YouTube), je Eintrag genau 1 Quell-Link\n - Scope 60/40 (Modelle/Tools).\ \ Wenn eine Kategorie nichts Neues hat: kurz 'nichts Neues'.\n - Am Ende\ \ NUR falls n\xF6tig eine Zeile: welche Quelle in sources.md erg\xE4nzt/entfernt\ \ werden sollte.\n\n Nutze web_search/web_fetch. Effort: medium.\n\n # 6.\ \ Weekly Bridge Tracker (Holzgartensteg)\n - name: \"Recherche: Holzgartensteg\ \ (w\xF6chentlich)\"\n declarationKey: \"recherche:holzgartensteg\"\n description:\ \ \"W\xF6chentlicher Status-Check zum Holzgartensteg Regensburg via ALLRIS & Web\"\ \n schedule:\n kind: \"cron\"\n expr: \"0 16 * * 5\"\n tz: \"\ Europe/Berlin\"\n agent: \"main\"\n session: \"isolated\"\n channel:\ \ \"telegram\"\n to: \"306373425\"\n announce: true\n message: |\n \ \ W\xF6chentlicher Recherche-Task (Holzgartensteg, Stadt Regensburg).\n\n \ \ AUFGABE: Pr\xFCfe, ob es NEUIGKEITEN zum geplanten Holzgartensteg (geplante\ \ Fu\xDF-/Radbr\xFCcke zwischen Grieser Spitz und Maria-Beer-Platz / Weichs) gibt\ \ \u2014 seit dem letzten Check bzw. den letzten ~7 Tagen.\n\n QUELLEN:\n\ \ 1) ALLRIS via Skill 'regensburg-stadtrat': Script /home/node/mop-skills/mop-skills/skills/regensburg-stadtrat/scripts/regensburg_ris.py\ \ \u2014 Befehle: search \"Holzgartensteg\" --limit 30 ; calendar --days 30 ;\ \ vorlage ; session . Achte auf NEUE Vorlagen, Beschl\xFCsse, Anfragen\ \ und Sitzungstermine (Ausschuss f\xFCr Stadtplanung, Verkehr und Wohnungsfragen\ \ / Stadtrat).\n 2) Lokale Medien via web_search: regensburg-digital.de,\ \ idowa.de, mittelbayerische.de, tvaktuell.com, regensburger-nachrichten.de.\n\ \n FOKUS: neue Vorlagen/Beschl\xFCsse, Ausschuss-/Stadtrattermine, Mittel/Budget,\ \ Planungsstand (Variante, Kosten, Termin), politische Positionen.\n\n BEKANNTER\ \ STAND (nicht wiederholen, nur Abweichungen melden): 2020 aus Investitionsprogramm\ \ gestrichen ('beerdigt'); Comeback ab 2025 (Gr\xFCne/Verb\xE4nde); Kommunalwahl\ \ 2026 mehrere Parteien daf\xFCr; Haushalt 2026: ~100.000 EUR Planungsmittel im\ \ Investitionsprogramm, Stadtrat-Beschluss 26.02.2026, Genehmigung Regierung der\ \ Oberpfalz 06.03.2026; Koalitionsvereinbarung 2026 mit Wiederaufnahme der Planungen.\n\ \n AUSGABE: Kurz auf Deutsch an Moritz. Falls echte Neuigkeiten: 3-6 Bullets\ \ mit Quelle+Link. Falls NICHTS Neues: eine kurze Zeile, z.B. 'Holzgartensteg:\ \ keine neuen Entwicklungen diese Woche.' Kein Hintergrund-Recycling.\n"