diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 6387687..4ecf9b7 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -2,7 +2,7 @@ "id": "memory-continuity", "name": "Memory Continuity", "description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.", - "version": "2.3.0", + "version": "2.4.0", "source": "https://github.com/dtzp555-max/memory-continuity", "configSchema": { "type": "object", diff --git a/scripts/post-install.sh b/scripts/post-install.sh index 4526d58..3f3ac68 100755 --- a/scripts/post-install.sh +++ b/scripts/post-install.sh @@ -4,7 +4,7 @@ # This script: # 1. Copies the plugin to ~/.openclaw/extensions/memory-continuity/ # 2. Adds the plugin entry to openclaw.json (if not present) -# 3. Restarts the gateway +# 3. Detects OpenClaw agents and installs SKILL.md to selected workspaces # # Usage: # bash scripts/post-install.sh @@ -50,16 +50,13 @@ if [[ ! -f "$CONFIG_FILE" ]]; then echo " WARNING: $CONFIG_FILE not found. Skipping config update." echo " You'll need to manually add the plugin entry." else - # Check if memory-continuity entry already exists - # Always ensure allow list, install record, and entry are present (idempotent) - python3 -c " + python3 -c " import json path = '$CONFIG_FILE' with open(path) as f: data = json.load(f) if 'plugins' not in data: data['plugins'] = {} -# Add to plugins.allow so OpenClaw trusts this plugin (no provenance warning) allow_list = data['plugins'].get('allow', []) if 'memory-continuity' not in allow_list: allow_list.append('memory-continuity') @@ -77,7 +74,6 @@ data['plugins']['entries']['memory-continuity'] = { 'autoExtract': True } } -# Add install record for provenance tracking (eliminates untracked-code warning) if 'installs' not in data['plugins']: data['plugins']['installs'] = {} data['plugins']['installs']['memory-continuity'] = { @@ -91,23 +87,142 @@ print(' Added plugin entry, trust config, and install record') " 2>/dev/null || echo " WARNING: Could not update config automatically. Add manually." fi -# Step 3: Restart gateway -echo "[3/3] Restarting gateway ..." -if command -v openclaw &>/dev/null; then - if openclaw gateway status 2>&1 | grep -q "running"; then - openclaw gateway restart 2>/dev/null && echo " Gateway restarted" || echo " Gateway restart failed — try: openclaw gateway restart" +# --------------------------------------------------------------------------- +# Step 3: Detect agents and install SKILL.md to selected workspaces +# --------------------------------------------------------------------------- +echo "[3/3] Detecting OpenClaw agents ..." + +# detect_agents outputs lines of: INDEX|ID|DISPLAY_NAME|WORKSPACE_PATH +# Uses python3 to parse openclaw.json; falls back gracefully if unavailable. +detect_agents() { + if [[ ! -f "$CONFIG_FILE" ]]; then + return 1 + fi + + python3 - "$CONFIG_FILE" "$OPENCLAW_DIR" <<'PYEOF' +import json, sys, os + +config_file = sys.argv[1] +openclaw_dir = sys.argv[2] + +with open(config_file) as f: + data = json.load(f) + +default_ws = data.get('defaults', {}).get('workspace', os.path.join(openclaw_dir, 'workspace', 'main')) + +agents = data.get('list', []) +seen = set() +idx = 1 + +for agent in agents: + agent_id = agent.get('id', '') + if not agent_id or agent_id in seen: + continue + seen.add(agent_id) + + name = agent.get('name', agent_id) + workspace = agent.get('workspace', default_ws if agent_id == 'main' else None) + + # Skip agents without a resolvable workspace + if not workspace: + workspace = os.path.join(openclaw_dir, 'workspaces', agent_id) + + # Expand ~ in path + workspace = os.path.expanduser(workspace) + + print('{}|{}|{}|{}'.format(idx, agent_id, name, workspace)) + idx += 1 + +PYEOF +} + +# Collect detected agents into arrays +AGENT_IDS=() +AGENT_NAMES=() +AGENT_WORKSPACES=() + +# Read detection output +if command -v python3 &>/dev/null && [[ -f "$CONFIG_FILE" ]]; then + while IFS='|' read -r idx agent_id agent_name workspace; do + AGENT_IDS+=("$agent_id") + AGENT_NAMES+=("$agent_name") + AGENT_WORKSPACES+=("$workspace") + done < <(detect_agents 2>/dev/null || true) +fi + +install_skill_to_workspace() { + local workspace="$1" + local skill_dest="${workspace}/skills/memory-continuity" + mkdir -p "$skill_dest" + cp "$REPO_DIR/SKILL.md" "$skill_dest/SKILL.md" + echo " Installed → ${skill_dest}/SKILL.md" +} + +if [[ ${#AGENT_IDS[@]} -eq 0 ]]; then + # Fallback: no agents detected — ask user for a workspace path + echo " No agents detected in openclaw.json (file missing or parse error)." + echo "" + printf " Enter workspace path to install SKILL.md (or press Enter to skip): " + read -r FALLBACK_WS + if [[ -n "$FALLBACK_WS" ]]; then + FALLBACK_WS="${FALLBACK_WS/#\~/$HOME}" + install_skill_to_workspace "$FALLBACK_WS" else - echo " Gateway not running — start it with: openclaw gateway start" + echo " Skipped SKILL.md installation." fi else - echo " openclaw command not found — install OpenClaw first" + # Show numbered list + echo "" + echo " Found ${#AGENT_IDS[@]} agent(s):" + for i in "${!AGENT_IDS[@]}"; do + num=$((i + 1)) + printf " [%d] %s (%s)\n" "$num" "${AGENT_NAMES[$i]}" "${AGENT_WORKSPACES[$i]}" + done + echo "" + echo " Install to: [A]ll agents / [1,2,...] specific (comma-separated) / [Q]uit" + printf " > " + read -r SELECTION + + case "${SELECTION^^}" in + A|ALL) + for i in "${!AGENT_IDS[@]}"; do + install_skill_to_workspace "${AGENT_WORKSPACES[$i]}" + done + ;; + Q|QUIT) + echo " Skipped SKILL.md installation." + ;; + *) + # Parse comma-separated numbers + IFS=',' read -ra PICKS <<< "$SELECTION" + INSTALLED=0 + for pick in "${PICKS[@]}"; do + pick="${pick// /}" # trim spaces + if [[ "$pick" =~ ^[0-9]+$ ]]; then + idx=$((pick - 1)) + if [[ $idx -ge 0 && $idx -lt ${#AGENT_IDS[@]} ]]; then + install_skill_to_workspace "${AGENT_WORKSPACES[$idx]}" + INSTALLED=$((INSTALLED + 1)) + else + echo " WARNING: No agent at index $pick — skipped." + fi + else + echo " WARNING: Invalid selection '$pick' — skipped." + fi + done + if [[ $INSTALLED -eq 0 ]]; then + echo " No valid selections — SKILL.md not installed." + fi + ;; + esac fi echo "" echo "=== Installation complete ===" echo "" echo "Verify with:" -echo " openclaw gateway restart 2>&1 | grep memory-continuity" +echo " bash scripts/verify.sh" +echo " bash scripts/verify.sh --all-agents" echo "" echo "Test with:" echo " 1. Tell your agent something memorable" diff --git a/scripts/verify.sh b/scripts/verify.sh index 36d9efd..d9c6ad7 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -2,11 +2,12 @@ # verify.sh — Verify memory-continuity installation (3-layer check) # # Usage: -# bash scripts/verify.sh [--workspace PATH] [--sample] +# bash scripts/verify.sh [--workspace PATH] [--sample] [--all-agents] # # Options: # --workspace PATH Override default workspace (~/.openclaw/workspace/main) # --sample Show a sample high-importance CURRENT_STATE.md +# --all-agents Run 3-layer check across all detected agent workspaces set -euo pipefail @@ -31,6 +32,7 @@ header() { echo -e "\n${BOLD}$*${RESET}"; } # --------------------------------------------------------------------------- WORKSPACE="${HOME}/.openclaw/workspace/main" SHOW_SAMPLE=false +ALL_AGENTS=false while [[ $# -gt 0 ]]; do case "$1" in @@ -38,6 +40,8 @@ while [[ $# -gt 0 ]]; do WORKSPACE="$2"; shift 2 ;; --sample) SHOW_SAMPLE=true; shift ;; + --all-agents) + ALL_AGENTS=true; shift ;; *) echo "Unknown argument: $1" >&2; exit 1 ;; esac @@ -85,6 +89,115 @@ EOF exit 0 fi +# --------------------------------------------------------------------------- +# --all-agents: detect all agent workspaces and run checks for each +# --------------------------------------------------------------------------- +OPENCLAW_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}" +CONFIG_FILE="$OPENCLAW_DIR/openclaw.json" +PLUGIN_DIR_SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +detect_all_agent_workspaces() { + if [[ ! -f "$CONFIG_FILE" ]] || ! command -v python3 &>/dev/null; then + return 1 + fi + python3 - "$CONFIG_FILE" "$OPENCLAW_DIR" <<'PYEOF' +import json, sys, os +config_file = sys.argv[1] +openclaw_dir = sys.argv[2] +with open(config_file) as f: + data = json.load(f) +default_ws = data.get('defaults', {}).get('workspace', os.path.join(openclaw_dir, 'workspace', 'main')) +seen = set() +for agent in data.get('list', []): + agent_id = agent.get('id', '') + if not agent_id or agent_id in seen: + continue + seen.add(agent_id) + name = agent.get('name', agent_id) + workspace = agent.get('workspace', default_ws if agent_id == 'main' else os.path.join(openclaw_dir, 'workspaces', agent_id)) + workspace = os.path.expanduser(workspace) + print('{}|{}|{}'.format(agent_id, name, workspace)) +PYEOF +} + +if $ALL_AGENTS; then + echo -e "\n${BOLD}memory-continuity — Multi-Agent Verifier${RESET}" + echo "────────────────────────────────────────────" + + AGENT_LINES=() + while IFS= read -r line; do + AGENT_LINES+=("$line") + done < <(detect_all_agent_workspaces 2>/dev/null || true) + + if [[ ${#AGENT_LINES[@]} -eq 0 ]]; then + echo -e "${RED}No agents detected. Is openclaw.json present and python3 available?${RESET}" + exit 1 + fi + + TOTAL_PASS=0 + TOTAL_FAIL=0 + + for line in "${AGENT_LINES[@]}"; do + IFS='|' read -r agent_id agent_name agent_ws <<< "$line" + echo -e "\n${BOLD}Agent: ${agent_name} (${agent_id})${RESET}" + echo -e " Workspace: ${agent_ws}" + + AGENT_ERRORS=0 + AGENT_WARNINGS=0 + + # Layer 1: SKILL.md in workspace + SKILL_PATH="${agent_ws}/skills/memory-continuity/SKILL.md" + if [[ -f "$SKILL_PATH" ]]; then + ok "SKILL.md installed" + else + fail "SKILL.md missing at ${SKILL_PATH}" + AGENT_ERRORS=$((AGENT_ERRORS + 1)) + fi + + # Layer 2: continuity_doctor.py + DOCTOR="${PLUGIN_DIR_SELF}/scripts/continuity_doctor.py" + if command -v python3 &>/dev/null && [[ -f "$DOCTOR" ]]; then + if python3 "$DOCTOR" --workspace "$agent_ws" &>/dev/null; then + ok "continuity_doctor.py passed" + else + warn "continuity_doctor.py exited non-zero" + AGENT_WARNINGS=$((AGENT_WARNINGS + 1)) + fi + else + warn "python3 or continuity_doctor.py unavailable — skipped" + AGENT_WARNINGS=$((AGENT_WARNINGS + 1)) + fi + + # Layer 3: CURRENT_STATE.md + STATE_FILE="${agent_ws}/memory/CURRENT_STATE.md" + if [[ ! -f "$STATE_FILE" ]]; then + warn "CURRENT_STATE.md not found (normal for new installs)" + AGENT_WARNINGS=$((AGENT_WARNINGS + 1)) + else + LINE_COUNT=$(grep -c '[^[:space:]]' "$STATE_FILE" || true) + if [[ $LINE_COUNT -gt 2 ]]; then + ok "CURRENT_STATE.md present (${LINE_COUNT} non-blank lines)" + else + warn "CURRENT_STATE.md appears empty or placeholder" + AGENT_WARNINGS=$((AGENT_WARNINGS + 1)) + fi + fi + + if [[ $AGENT_ERRORS -eq 0 ]]; then + echo -e " ${GREEN}→ PASS${RESET} (${AGENT_WARNINGS} warning(s))" + TOTAL_PASS=$((TOTAL_PASS + 1)) + else + echo -e " ${RED}→ FAIL${RESET} (${AGENT_ERRORS} error(s), ${AGENT_WARNINGS} warning(s))" + TOTAL_FAIL=$((TOTAL_FAIL + 1)) + fi + done + + echo "" + echo "────────────────────────────────────────────" + echo -e "${BOLD}Summary: ${TOTAL_PASS} passed, ${TOTAL_FAIL} failed (of ${#AGENT_LINES[@]} agents)${RESET}" + [[ $TOTAL_FAIL -gt 0 ]] && exit 1 || exit 0 +fi + # --------------------------------------------------------------------------- # Header # ---------------------------------------------------------------------------