diff --git a/apps/memos-local-openclaw/index.ts b/apps/memos-local-openclaw/index.ts index 47ebfdd13..42172f2b4 100644 --- a/apps/memos-local-openclaw/index.ts +++ b/apps/memos-local-openclaw/index.ts @@ -1779,31 +1779,6 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`, { name: "network_skill_pull" }, ); - // ─── Inject recall context as system message (hidden from chat UI) ─── - // `prependContext` (from before_agent_start) is prepended to user messages and - // therefore visible in the chat box. To keep injected memories invisible to the - // user while still feeding them to the model, we stash the recall result in - // `pendingRecallContext` and inject it as a system-level message via the - // `before_context_send` hook, which fires synchronously during prompt assembly. - let pendingRecallContext: string | null = null; - - api.on("before_context_send", (event: { messages: Array<{ role: string; content: string | unknown }> }) => { - if (!pendingRecallContext) return; - const memoryContext = pendingRecallContext; - pendingRecallContext = null; - // Insert after the last system message (before the first user/assistant turn). - // This keeps the static system prompt at the very beginning of the sequence so - // KV-cache prefixes stay stable across requests — the provider can reuse the - // cached keys/values for the system prompt even though the memory block changes. - const firstNonSystemIdx = event.messages.findIndex((m) => m.role !== "system"); - if (firstNonSystemIdx === -1) { - event.messages.push({ role: "system", content: memoryContext }); - } else { - event.messages.splice(firstNonSystemIdx, 0, { role: "system", content: memoryContext }); - } - ctx.log.info(`before_context_send: injected recall context as system message at idx=${firstNonSystemIdx === -1 ? event.messages.length - 1 : firstNonSystemIdx} (${memoryContext.length} chars)`); - }); - // ─── Auto-recall: inject relevant memories before agent starts ─── api.on("before_prompt_build", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => { @@ -1909,8 +1884,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`, "\n\nYou SHOULD call `skill_get` to retrieve the full guide before attempting the task."; ctx.log.info(`auto-recall-skill (no-memory path): injecting ${topSkills.length} skill(s)`); try { store.recordApiLog("skill_search", { type: "auto_recall_skill", query }, JSON.stringify(topSkills), dur, true); } catch { /* best-effort */ } - pendingRecallContext = skillContext; - return {}; + return { prependContext: skillContext }; } } catch (err) { ctx.log.debug(`auto-recall-skill (no-memory path): failed: ${err}`); @@ -1919,11 +1893,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`, if (query.length > 50) { const noRecallHint = - "## Memory system\n\n" + + "## Memory system — ACTION REQUIRED\n\n" + "Auto-recall found no results for a long query. " + - "Call `memory_search` with a shortened query (2-5 key words) before answering."; - pendingRecallContext = noRecallHint; - return {}; + "You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " + + "Do NOT skip this step. Do NOT answer without searching first."; + return { prependContext: noRecallHint }; } return; } @@ -1959,7 +1933,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`, }), dur, true); if (query.length > 50) { const noRecallHint = - "## Memory system\n\n" + + "## Memory system — ACTION REQUIRED\n\n" + "Auto-recall found no relevant results for a long query. " + "You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " + "Do NOT skip this step. Do NOT answer without searching first."; @@ -2011,9 +1985,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`, const tipsText = "\n\nAvailable follow-up tools:\n" + tips.join("\n"); const contextParts = [ - "## Recalled memories", + "## User's conversation history (from memory system)", "", - "The following facts were retrieved from previous conversations with this user. Treat them as established knowledge.", + "IMPORTANT: The following are facts from previous conversations with this user.", + "You MUST treat these as established knowledge and use them directly when answering.", + "Do NOT say you don't know or don't have information if the answer is in these memories.", "", lines.join("\n\n"), ]; @@ -2097,18 +2073,18 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`, }), recallDur, true); telemetry.trackAutoRecall(filteredHits.length, recallDur); - ctx.log.info(`auto-recall: stashing recall context for system message injection (${context.length} chars), sufficient=${sufficient}, skills=${skillSection ? "yes" : "no"}`); + ctx.log.info(`auto-recall: returning prependContext (${context.length} chars), sufficient=${sufficient}, skills=${skillSection ? "yes" : "no"}`); if (!sufficient) { const searchHint = "\n\nIf these memories don't fully answer the question, " + "call `memory_search` with a shorter or rephrased query to find more."; - pendingRecallContext = context + searchHint; - return {}; + return { prependContext: context + searchHint }; } - pendingRecallContext = context; - return {}; + return { + prependContext: context, + }; } catch (err) { const dur = performance.now() - recallT0; store.recordToolCall("memory_search", dur, false); diff --git a/apps/memos-local-openclaw/install.ps1 b/apps/memos-local-openclaw/install.ps1 deleted file mode 100644 index c78e894dd..000000000 --- a/apps/memos-local-openclaw/install.ps1 +++ /dev/null @@ -1,383 +0,0 @@ -$ErrorActionPreference = "Stop" -if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { - $PSNativeCommandUseErrorActionPreference = $false -} -$env:NPM_CONFIG_LOGLEVEL = "error" - -function Write-Info { - param([string]$Message) - Write-Host $Message -ForegroundColor Cyan -} - -function Write-Success { - param([string]$Message) - Write-Host $Message -ForegroundColor Green -} - -function Write-Warn { - param([string]$Message) - Write-Host $Message -ForegroundColor Yellow -} - -function Write-Err { - param([string]$Message) - Write-Host $Message -ForegroundColor Red -} - -function Get-NodeMajorVersion { - $nodeCommand = Get-Command node -ErrorAction SilentlyContinue - if (-not $nodeCommand) { - return 0 - } - $versionRaw = & node -v 2>$null - if (-not $versionRaw) { - return 0 - } - $trimmed = $versionRaw.TrimStart("v") - $majorText = $trimmed.Split(".")[0] - $major = 0 - if ([int]::TryParse($majorText, [ref]$major)) { - return $major - } - return 0 -} - -function Update-SessionPath { - $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine") - $userPath = [Environment]::GetEnvironmentVariable("Path", "User") - $env:Path = "$machinePath;$userPath" -} - -function Install-Node { - if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { - Write-Err "winget is required for automatic Node.js installation on Windows." - Write-Err "Install Node.js 22 or newer manually from https://nodejs.org and rerun this script." - exit 1 - } - - Write-Info "Installing Node.js via winget..." - & winget install OpenJS.NodeJS --accept-package-agreements --accept-source-agreements --silent - Update-SessionPath -} - -function Ensure-Node22 { - $requiredMajor = 22 - $currentMajor = Get-NodeMajorVersion - if ($currentMajor -ge $requiredMajor) { - Write-Success "Node.js version check passed (>= $requiredMajor)." - return - } - - Write-Warn "Node.js >= $requiredMajor is required." - Write-Warn "Node.js is missing or too old. Starting automatic installation..." - Install-Node - - $currentMajor = Get-NodeMajorVersion - if ($currentMajor -ge $requiredMajor) { - $currentVersion = & node -v - Write-Success "Node.js is ready: $currentVersion" - return - } - - Write-Err "Node.js installation did not meet version >= $requiredMajor." - exit 1 -} - -function Print-Banner { - Write-Host "Memos Local OpenClaw Installer" -ForegroundColor Cyan - Write-Host "Memos Local Memory for OpenClaw." -ForegroundColor Cyan - Write-Host "Keep your context, tasks, and recall in one local memory engine." -ForegroundColor Yellow -} - -function Parse-Arguments { - param([string[]]$RawArgs) - - $result = @{ - PluginVersion = "latest" - Port = "18789" - OpenClawHome = (Join-Path $HOME ".openclaw") - } - - $index = 0 - while ($index -lt $RawArgs.Count) { - $arg = $RawArgs[$index] - switch ($arg) { - "--version" { - if ($index + 1 -ge $RawArgs.Count) { - Write-Err "Missing value for --version." - exit 1 - } - $result.PluginVersion = $RawArgs[$index + 1] - $index += 2 - } - "--port" { - if ($index + 1 -ge $RawArgs.Count) { - Write-Err "Missing value for --port." - exit 1 - } - $result.Port = $RawArgs[$index + 1] - $index += 2 - } - "--openclaw-home" { - if ($index + 1 -ge $RawArgs.Count) { - Write-Err "Missing value for --openclaw-home." - exit 1 - } - $result.OpenClawHome = $RawArgs[$index + 1] - $index += 2 - } - default { - Write-Err "Unknown argument: $arg" - Write-Warn "Usage: .\apps\install.ps1 [--version ] [--port ] [--openclaw-home ]" - exit 1 - } - } - } - - if ([string]::IsNullOrWhiteSpace($result.PluginVersion) -or - [string]::IsNullOrWhiteSpace($result.Port) -or - [string]::IsNullOrWhiteSpace($result.OpenClawHome)) { - Write-Err "Arguments cannot be empty." - exit 1 - } - - return $result -} - -function Update-OpenClawConfig { - param( - [string]$OpenClawHome, - [string]$ConfigPath, - [string]$PluginId - ) - - Write-Info "Updating OpenClaw config..." - New-Item -ItemType Directory -Path $OpenClawHome -Force | Out-Null - $nodeScript = @' -const fs = require("fs"); - -const configPath = process.argv[2]; -const pluginId = process.argv[3]; - -let config = {}; -if (fs.existsSync(configPath)) { - const raw = fs.readFileSync(configPath, "utf8").trim(); - if (raw.length > 0) { - const parsed = JSON.parse(raw); - if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { - config = parsed; - } - } -} - -if (!config.plugins || typeof config.plugins !== "object" || Array.isArray(config.plugins)) { - config.plugins = {}; -} - -config.plugins.enabled = true; - -if (!Array.isArray(config.plugins.allow)) { - config.plugins.allow = []; -} - -if (!config.plugins.allow.includes(pluginId)) { - config.plugins.allow.push(pluginId); -} - -fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); -'@ - $nodeScript | & node - $ConfigPath $PluginId - Write-Success "OpenClaw config updated: $ConfigPath" -} - -function Ensure-PluginDirRemovedByUninstall { - param([string]$ExtensionDir, [string]$PluginId) - - $preservedDistPath = "" - if (Test-Path $ExtensionDir) { - Write-Warn "Plugin directory still exists after uninstall: $ExtensionDir" - Write-Warn "Preparing plugin directory for reinstall while preserving dist..." - - $distPath = Join-Path $ExtensionDir "dist" - if (Test-Path $distPath) { - $backupRoot = Join-Path $env:TEMP ("memos-local-openclaw-dist-" + [guid]::NewGuid().ToString("N")) - New-Item -ItemType Directory -Path $backupRoot -Force | Out-Null - $preservedDistPath = Join-Path $backupRoot "dist" - try { - Move-Item -LiteralPath $distPath -Destination $preservedDistPath -Force -ErrorAction Stop - Write-Info "Preserved dist for reinstall: $preservedDistPath" - } - catch { - Write-Err "Failed to preserve dist directory before cleanup." - Write-Err $_.Exception.Message - exit 1 - } - } - - $items = Get-ChildItem -LiteralPath $ExtensionDir -Force -ErrorAction SilentlyContinue - foreach ($item in $items) { - try { - Remove-Item -LiteralPath $item.FullName -Recurse -Force -ErrorAction Stop - } - catch { - Write-Err "Failed to remove leftover item: $($item.FullName)" - Write-Err $_.Exception.Message - exit 1 - } - } - - $remaining = Get-ChildItem -LiteralPath $ExtensionDir -Force -ErrorAction SilentlyContinue - $nonDistRemaining = @($remaining | Where-Object { $_.Name -ine "dist" }) - if ($nonDistRemaining.Count -gt 0) { - Write-Err "Leftover files still exist after cleanup." - $nonDistRemaining | ForEach-Object { Write-Host $_.FullName } - exit 1 - } - - try { - Remove-Item -LiteralPath $ExtensionDir -Recurse -Force -ErrorAction Stop - } - catch { - Write-Err "Failed to remove plugin directory before reinstall: $ExtensionDir" - Write-Err $_.Exception.Message - exit 1 - } - - if (Test-Path $ExtensionDir) { - Write-Err "Plugin directory still exists before reinstall: $ExtensionDir" - exit 1 - } - - Write-Success "Plugin directory prepared for reinstall." - } - - return $preservedDistPath -} - -function Restore-PreservedDistIfNeeded { - param([string]$PreservedDistPath, [string]$ExtensionDir) - if ([string]::IsNullOrWhiteSpace($PreservedDistPath) -or -not (Test-Path $PreservedDistPath)) { - return - } - - $targetDistPath = Join-Path $ExtensionDir "dist" - $backupRoot = Split-Path -Path $PreservedDistPath -Parent - try { - if (Test-Path $targetDistPath) { - $legacyDistPath = Join-Path $ExtensionDir ("dist_preserved_" + (Get-Date -Format "yyyyMMddHHmmss")) - Move-Item -LiteralPath $PreservedDistPath -Destination $legacyDistPath -Force -ErrorAction Stop - Write-Warn "Installer created a new dist. Previous dist was preserved at: $legacyDistPath" - } - else { - Move-Item -LiteralPath $PreservedDistPath -Destination $targetDistPath -Force -ErrorAction Stop - Write-Success "Restored preserved dist to: $targetDistPath" - } - } - catch { - Write-Err "Failed to restore preserved dist." - Write-Err $_.Exception.Message - exit 1 - } - finally { - if (Test-Path $backupRoot) { - Remove-Item -LiteralPath $backupRoot -Recurse -Force -ErrorAction SilentlyContinue - } - } -} - -function Uninstall-PluginIfPresent { - param([string]$PluginId) - - $outputLines = @() - try { - $outputLines = "y`n" | & npx openclaw plugins uninstall $PluginId 2>&1 - } - catch { - $outputLines += ($_ | Out-String) - } - - $outputText = ($outputLines | Out-String) - if (-not [string]::IsNullOrWhiteSpace($outputText) -and ($outputText -notmatch "Plugin not found")) { - Write-Warn "Uninstall returned messages and will be ignored to match install.sh behavior." - } - Write-Info "Uninstall step completed (best effort)." -} - -$parsed = Parse-Arguments -RawArgs $args -$PluginVersion = $parsed.PluginVersion -$Port = $parsed.Port -$OpenClawHome = $parsed.OpenClawHome - -$PluginId = "memos-local-openclaw-plugin" -$PluginPackage = "@memtensor/memos-local-openclaw-plugin" -$PackageSpec = "$PluginPackage@$PluginVersion" -$ExtensionDir = Join-Path $OpenClawHome "extensions\$PluginId" -$OpenClawConfigPath = Join-Path $OpenClawHome "openclaw.json" - -Print-Banner -Ensure-Node22 - -if (-not (Get-Command npx -ErrorAction SilentlyContinue)) { - Write-Err "npx was not found after Node.js setup." - exit 1 -} - -if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - Write-Err "npm was not found after Node.js setup." - exit 1 -} - -if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Write-Err "node was not found after setup." - exit 1 -} - -Write-Info "Stopping OpenClaw Gateway..." -try { - & npx openclaw gateway stop *> $null -} -catch { - Write-Warn "OpenClaw gateway stop returned an error. Continuing..." -} - -$portNumber = 0 -if ([int]::TryParse($Port, [ref]$portNumber)) { - $connections = Get-NetTCPConnection -LocalPort $portNumber -ErrorAction SilentlyContinue - if ($connections) { - $pids = $connections | Select-Object -ExpandProperty OwningProcess -Unique - if ($pids) { - Write-Warn "Processes still using port $Port. Killing PID(s): $($pids -join ', ')" - foreach ($processId in $pids) { - Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue - } - } - } -} - -Write-Info "Uninstalling existing plugin if present..." -Uninstall-PluginIfPresent -PluginId $PluginId -$preservedDistPath = Ensure-PluginDirRemovedByUninstall -ExtensionDir $ExtensionDir -PluginId $PluginId - -Write-Info "Installing plugin $PackageSpec..." -& npx openclaw plugins install $PackageSpec - -if (-not (Test-Path $ExtensionDir)) { - Write-Err "Plugin directory was not found: $ExtensionDir" - exit 1 -} - -Restore-PreservedDistIfNeeded -PreservedDistPath $preservedDistPath -ExtensionDir $ExtensionDir - -Write-Info "Rebuilding better-sqlite3..." -Push-Location $ExtensionDir -try { - & npm rebuild better-sqlite3 -} -finally { - Pop-Location -} - -Update-OpenClawConfig -OpenClawHome $OpenClawHome -ConfigPath $OpenClawConfigPath -PluginId $PluginId - -Write-Success "Restarting OpenClaw Gateway..." -& npx openclaw gateway run --port $Port --force diff --git a/apps/memos-local-openclaw/install.sh b/apps/memos-local-openclaw/install.sh deleted file mode 100644 index deb027a48..000000000 --- a/apps/memos-local-openclaw/install.sh +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -BOLD='\033[1m' -NC='\033[0m' -DEFAULT_TAGLINE="Memos Local Memory for OpenClaw." -DEFAULT_SUBTITLE="Keep your context, tasks, and recall in one local memory engine." - -info() { - echo -e "${BLUE}$1${NC}" -} - -success() { - echo -e "${GREEN}$1${NC}" -} - -warn() { - echo -e "${YELLOW}$1${NC}" -} - -error() { - echo -e "${RED}$1${NC}" -} - -node_major_version() { - if ! command -v node >/dev/null 2>&1; then - echo "0" - return 0 - fi - local node_version - node_version="$(node -v 2>/dev/null || true)" - node_version="${node_version#v}" - echo "${node_version%%.*}" -} - -run_with_privilege() { - if [[ "$(id -u)" -eq 0 ]]; then - "$@" - else - sudo "$@" - fi -} - -download_to_file() { - local url="$1" - local output="$2" - if command -v curl >/dev/null 2>&1; then - curl -fsSL --proto '=https' --tlsv1.2 "$url" -o "$output" - return 0 - fi - if command -v wget >/dev/null 2>&1; then - wget -q --https-only --secure-protocol=TLSv1_2 "$url" -O "$output" - return 0 - fi - return 1 -} - -install_node22() { - local os_name - os_name="$(uname -s)" - - if [[ "$os_name" == "Darwin" ]]; then - if ! command -v brew >/dev/null 2>&1; then - error "Homebrew is required to auto-install Node.js on macOS, macOS 自动安装 Node.js 需要 Homebrew" - error "Install Homebrew first, 请先安装 Homebrew: https://brew.sh" - exit 1 - fi - info "Auto install Node.js 22 via Homebrew, 通过 Homebrew 自动安装 Node.js 22..." - brew install node@22 >/dev/null - brew link node@22 --overwrite --force >/dev/null 2>&1 || true - local brew_node_prefix - brew_node_prefix="$(brew --prefix node@22 2>/dev/null || true)" - if [[ -n "$brew_node_prefix" && -x "${brew_node_prefix}/bin/node" ]]; then - export PATH="${brew_node_prefix}/bin:${PATH}" - fi - return 0 - fi - - if [[ "$os_name" == "Linux" ]]; then - info "Auto install Node.js 22 on Linux, 在 Linux 自动安装 Node.js 22..." - local tmp_script - tmp_script="$(mktemp)" - if command -v apt-get >/dev/null 2>&1; then - if ! download_to_file "https://deb.nodesource.com/setup_22.x" "$tmp_script"; then - error "Failed to download NodeSource setup script, 下载 NodeSource 脚本失败" - rm -f "$tmp_script" - exit 1 - fi - run_with_privilege bash "$tmp_script" - run_with_privilege apt-get update -qq - run_with_privilege apt-get install -y -qq nodejs - rm -f "$tmp_script" - return 0 - fi - if command -v dnf >/dev/null 2>&1; then - if ! download_to_file "https://rpm.nodesource.com/setup_22.x" "$tmp_script"; then - error "Failed to download NodeSource setup script, 下载 NodeSource 脚本失败" - rm -f "$tmp_script" - exit 1 - fi - run_with_privilege bash "$tmp_script" - run_with_privilege dnf install -y -q nodejs - rm -f "$tmp_script" - return 0 - fi - if command -v yum >/dev/null 2>&1; then - if ! download_to_file "https://rpm.nodesource.com/setup_22.x" "$tmp_script"; then - error "Failed to download NodeSource setup script, 下载 NodeSource 脚本失败" - rm -f "$tmp_script" - exit 1 - fi - run_with_privilege bash "$tmp_script" - run_with_privilege yum install -y -q nodejs - rm -f "$tmp_script" - return 0 - fi - rm -f "$tmp_script" - fi - - error "Unsupported platform for auto-install, 当前平台不支持自动安装 Node.js 22" - error "Please install Node.js >=22 manually, 请手动安装 Node.js >=22" - exit 1 -} - -ensure_node22() { - local required_major="22" - local current_major - current_major="$(node_major_version)" - - if [[ "$current_major" =~ ^[0-9]+$ ]] && (( current_major >= required_major )); then - success "Node.js version check passed (>= ${required_major}), Node.js 版本检查通过 (>= ${required_major})" - return 0 - fi - - warn "Node.js >= ${required_major} is required, 需要 Node.js >= ${required_major}" - warn "Current Node.js is too old or missing, 当前 Node.js 版本过低或不存在,开始自动安装..." - install_node22 - - current_major="$(node_major_version)" - if [[ "$current_major" =~ ^[0-9]+$ ]] && (( current_major >= required_major )); then - success "Node.js upgraded and ready, Node.js 已升级并可用: $(node -v)" - return 0 - fi - - error "Node.js installation did not meet >= ${required_major}, Node.js 安装后仍不满足 >= ${required_major}" - exit 1 -} - -print_banner() { - echo -e "${BLUE}${BOLD}🧠 Memos Local OpenClaw Installer${NC}" - echo -e "${BLUE}${DEFAULT_TAGLINE}${NC}" - echo -e "${YELLOW}${DEFAULT_SUBTITLE}${NC}" -} - -PLUGIN_ID="memos-local-openclaw-plugin" -PLUGIN_PACKAGE="@memtensor/memos-local-openclaw-plugin" -PLUGIN_VERSION="latest" -PORT="18789" -OPENCLAW_HOME="${HOME}/.openclaw" - -while [[ $# -gt 0 ]]; do - case "$1" in - --version) - PLUGIN_VERSION="${2:-}" - shift 2 - ;; - --port) - PORT="${2:-}" - shift 2 - ;; - --openclaw-home) - OPENCLAW_HOME="${2:-}" - shift 2 - ;; - *) - error "Unknown argument, 未知参数: $1" - warn "Usage, 用法: bash apps/openclaw-memos-plugin-install.sh [--version <版本>] [--port <端口>] [--openclaw-home <路径>]" - exit 1 - ;; - esac -done - -if [[ -z "$PLUGIN_VERSION" || -z "$PORT" || -z "$OPENCLAW_HOME" ]]; then - error "Arguments cannot be empty, 参数不能为空" - exit 1 -fi - -print_banner - -ensure_node22 - -if ! command -v npx >/dev/null 2>&1; then - error "npx not found after Node.js setup, Node.js 安装后仍未找到 npx" - exit 1 -fi - -if ! command -v npm >/dev/null 2>&1; then - error "npm not found after Node.js setup, Node.js 安装后仍未找到 npm" - exit 1 -fi - -if ! command -v node >/dev/null 2>&1; then - error "node not found after setup, 环境初始化后仍未找到 node" - exit 1 -fi - -PACKAGE_SPEC="${PLUGIN_PACKAGE}@${PLUGIN_VERSION}" -EXTENSION_DIR="${OPENCLAW_HOME}/extensions/${PLUGIN_ID}" -OPENCLAW_CONFIG_PATH="${OPENCLAW_HOME}/openclaw.json" - -update_openclaw_config() { - info "Update OpenClaw config, 更新 OpenClaw 配置..." - mkdir -p "${OPENCLAW_HOME}" - node - "${OPENCLAW_CONFIG_PATH}" "${PLUGIN_ID}" <<'NODE' -const fs = require('fs'); - -const configPath = process.argv[2]; -const pluginId = process.argv[3]; - -let config = {}; -if (fs.existsSync(configPath)) { - const raw = fs.readFileSync(configPath, 'utf8').trim(); - if (raw.length > 0) { - const parsed = JSON.parse(raw); - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { - config = parsed; - } - } -} - -if (!config.plugins || typeof config.plugins !== 'object' || Array.isArray(config.plugins)) { - config.plugins = {}; -} - -config.plugins.enabled = true; - -if (!Array.isArray(config.plugins.allow)) { - config.plugins.allow = []; -} - -if (!config.plugins.allow.includes(pluginId)) { - config.plugins.allow.push(pluginId); -} - -fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); -NODE - success "OpenClaw config updated, OpenClaw 配置已更新: ${OPENCLAW_CONFIG_PATH}" -} - -ensure_plugin_dir_removed_by_uninstall() { - if [[ -d "${EXTENSION_DIR}" ]]; then - error "Plugin directory still exists after uninstall, 卸载后插件目录仍存在: ${EXTENSION_DIR}" - warn "Run this command and retry, 请先执行以下命令后重试: echo \"y\" | npx openclaw plugins uninstall ${PLUGIN_ID}" - exit 1 - fi -} - -info "Stop OpenClaw Gateway, 停止 OpenClaw Gateway..." -npx openclaw gateway stop >/dev/null 2>&1 || true - -if command -v lsof >/dev/null 2>&1; then - PIDS="$(lsof -i :"${PORT}" -t 2>/dev/null || true)" - if [[ -n "$PIDS" ]]; then - warn "Processes still on port ${PORT}, 检测到端口 ${PORT} 仍有进程,占用 PID: ${PIDS}" - echo "$PIDS" | xargs kill -9 >/dev/null 2>&1 || true - fi -fi - -info "Uninstall old plugin if exists, 卸载旧插件(若存在)..." -printf "y\n" | npx openclaw plugins uninstall "${PLUGIN_ID}" >/dev/null 2>&1 || true -ensure_plugin_dir_removed_by_uninstall - -info "Install plugin ${PACKAGE_SPEC}, 安装插件 ${PACKAGE_SPEC}..." -npx openclaw plugins install "${PACKAGE_SPEC}" - -if [[ ! -d "$EXTENSION_DIR" ]]; then - error "Plugin directory not found, 未找到插件目录: ${EXTENSION_DIR}" - exit 1 -fi - -info "Rebuild better-sqlite3, 重编译 better-sqlite3..." -( - cd "$EXTENSION_DIR" - npm rebuild better-sqlite3 -) - -update_openclaw_config - -success "Restart OpenClaw Gateway, 重启 OpenClaw Gateway..." -exec npx openclaw gateway run --port "${PORT}" --force