941 lines
81 KiB
YAML
941 lines
81 KiB
YAML
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]\
|
|
\ <file>...\\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}] <unparseable event json>\")\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 <ID> | --agent <NAME>) [--limit <N>]\")\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 <ID> ; session <ID>. 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"
|