diff --git a/examples/openclaw-plugin/INSTALL-ZH.md b/examples/openclaw-plugin/INSTALL-ZH.md index 30d05af65..53aa99329 100644 --- a/examples/openclaw-plugin/INSTALL-ZH.md +++ b/examples/openclaw-plugin/INSTALL-ZH.md @@ -107,7 +107,7 @@ npx -y -p openclaw-openviking-setup-helper ov-install -y | 参数 | 含义 | | --- | --- | | `--github-repo owner/repo` | 从哪个 GitHub 仓库拉取插件文件(默认:`volcengine/OpenViking`) | -| `--plugin-version REF` | 分支、tag 或 commit,用于拉取插件(默认:`main`) | +| `--plugin-version REF` | 分支、tag 或 commit,用于拉取插件(默认:`volcengine/OpenViking` 仓库中的最新 tag) | | `--openviking-version VER` | 固定为 `pip install openviking==VER`(不写则安装 PyPI 最新版) | 示例: @@ -116,7 +116,7 @@ npx -y -p openclaw-openviking-setup-helper ov-install -y # 安装指定 tag 版本的插件(例如 v0.2.9) ov-install -y --plugin-version v0.2.9 -# 固定 PyPI 上的 OpenViking 版本,插件仍从 main 拉取 +# 固定 PyPI 上的 OpenViking 版本,插件默认仍跟随仓库最新 tag ov-install -y --openviking-version 0.2.9 # 同时指定插件 tag 和 OpenViking PyPI 版本 @@ -211,7 +211,7 @@ curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples | 参数 / 环境变量 | 含义 | | --- | --- | | `--repo owner/repo`、`REPO` | 从哪个 GitHub 仓库拉取插件文件(默认:`volcengine/OpenViking`) | -| `--plugin-version REF`、`PLUGIN_VERSION` | 分支、tag 或 commit(默认:`main`;兼容旧变量 `BRANCH`) | +| `--plugin-version REF`、`PLUGIN_VERSION` | 分支、tag 或 commit(默认:最新 tag;兼容旧变量 `BRANCH`) | | `--openviking-version VER`、`OPENVIKING_VERSION` | `pip install openviking==VER`(不写则安装 PyPI 最新版) | 示例: diff --git a/examples/openclaw-plugin/INSTALL.md b/examples/openclaw-plugin/INSTALL.md index 330d487f3..c652492a1 100644 --- a/examples/openclaw-plugin/INSTALL.md +++ b/examples/openclaw-plugin/INSTALL.md @@ -118,7 +118,7 @@ Use **`ov-install`** to install a **specific archived plugin version** or pin th | Flag | Meaning | | --- | --- | | `--github-repo owner/repo` | GitHub repo for plugin raw downloads (default: `volcengine/OpenViking`) | -| `--plugin-version REF` | Git branch, tag, or commit for plugin files (default: `main`) | +| `--plugin-version REF` | Git branch, tag, or commit for plugin files (default: latest Git tag in `volcengine/OpenViking`) | | `--openviking-version VER` | Pin `pip install openviking==VER` (omit for latest PyPI release) | Examples: @@ -127,7 +127,7 @@ Examples: # Install a specific tagged plugin version (e.g. v0.2.9) ov-install -y --plugin-version v0.2.9 -# Pin OpenViking on PyPI, plugin from main +# Pin OpenViking on PyPI, plugin still follows the repo's latest tag by default ov-install -y --openviking-version 0.2.9 # Pin both plugin tag and OpenViking PyPI version @@ -206,7 +206,7 @@ curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples | Flag / env | Meaning | | --- | --- | | `--repo owner/repo` / `REPO` | GitHub repo for plugin raw files (default: `volcengine/OpenViking`) | -| `--plugin-version REF` / `PLUGIN_VERSION` | Git branch, tag, or commit (default: `main`; legacy: `BRANCH`) | +| `--plugin-version REF` / `PLUGIN_VERSION` | Git branch, tag, or commit (default: latest Git tag; legacy: `BRANCH`) | | `--openviking-version VER` / `OPENVIKING_VERSION` | `pip install openviking==VER` (omit for latest) | Examples: diff --git a/examples/openclaw-plugin/install.sh b/examples/openclaw-plugin/install.sh index 678cf901c..ef7ae6a8f 100755 --- a/examples/openclaw-plugin/install.sh +++ b/examples/openclaw-plugin/install.sh @@ -5,7 +5,7 @@ # # Options: # --repo - GitHub repository (default: volcengine/OpenViking) -# --plugin-version - Plugin version (Git tag, e.g. v0.2.9, default: main) +# --plugin-version - Plugin version (Git tag, e.g. v0.2.9, default: latest tag) # --openviking-version - OpenViking PyPI version (e.g. 0.2.9, default: latest) # --workdir - OpenClaw config directory (default: ~/.openclaw) # --update / --upgrade-plugin - Upgrade only the plugin using native script logic @@ -45,8 +45,12 @@ set -o pipefail OPENVIKING_PYTHON_PATH="" REPO="${REPO:-volcengine/OpenViking}" -# BRANCH is legacy, prefer PLUGIN_VERSION -PLUGIN_VERSION="${PLUGIN_VERSION:-${BRANCH:-main}}" +# BRANCH is legacy, prefer PLUGIN_VERSION. If omitted, resolve the latest tag from GitHub. +PLUGIN_VERSION_EXPLICIT="0" +if [[ -n "${PLUGIN_VERSION:-}" || -n "${BRANCH:-}" ]]; then + PLUGIN_VERSION_EXPLICIT="1" +fi +PLUGIN_VERSION="${PLUGIN_VERSION:-${BRANCH:-}}" OPENVIKING_VERSION="${OPENVIKING_VERSION:-}" INSTALL_YES="${OPENVIKING_INSTALL_YES:-0}" UPGRADE_PLUGIN_ONLY="${OPENVIKING_UPGRADE_PLUGIN_ONLY:-0}" @@ -115,6 +119,7 @@ for arg in "$@"; do fi if [[ -n "$_expect_plugin_version" ]]; then PLUGIN_VERSION="$arg" + PLUGIN_VERSION_EXPLICIT="1" _expect_plugin_version="" continue fi @@ -141,7 +146,7 @@ for arg in "$@"; do echo "" echo "Options:" echo " --repo GitHub repository (default: volcengine/OpenViking)" - echo " --plugin-version Plugin version (Git tag, e.g. v0.2.9, default: main)" + echo " --plugin-version Plugin version (Git tag, e.g. v0.2.9, default: latest tag)" echo " --openviking-version OpenViking PyPI version (e.g. 0.2.9, default: latest)" echo " --workdir OpenClaw config directory (default: ~/.openclaw)" echo " --update, --upgrade-plugin" @@ -161,7 +166,7 @@ for arg in "$@"; do echo " # Install specific plugin version" echo " curl -fsSL | bash -s -- --plugin-version v0.2.8" echo "" - echo " # Upgrade only the plugin files" + echo " # Upgrade only the plugin files from main branch" echo " curl -fsSL | bash -s -- --update --plugin-version main" echo "" echo " # Roll back the last plugin upgrade" @@ -1126,6 +1131,73 @@ is_semver_like() { [[ "$1" =~ ^v?[0-9]+(\.[0-9]+){1,2}$ ]] } +pick_latest_plugin_tag() { + local latest_semver="" + local first_tag="" + local tag="" + while IFS= read -r tag; do + [[ -z "$tag" ]] && continue + [[ -z "$first_tag" ]] && first_tag="$tag" + if is_semver_like "$tag"; then + if [[ -z "$latest_semver" ]] || version_gte "$tag" "$latest_semver"; then + latest_semver="$tag" + fi + fi + done + + if [[ -n "$latest_semver" ]]; then + printf '%s\n' "$latest_semver" + return 0 + fi + + if [[ -n "$first_tag" ]]; then + printf '%s\n' "$first_tag" + fi +} + +resolve_default_plugin_version() { + [[ -n "$PLUGIN_VERSION" ]] && return 0 + + info "$(tr "No plugin version specified; resolving latest tag from ${REPO}..." "未指定插件版本,正在解析 ${REPO} 的最新 tag...")" + + local latest_tag="" + local api_tags="" + api_tags="$( + curl -fsSL --connect-timeout 10 \ + -H "Accept: application/vnd.github+json" \ + -H "User-Agent: openviking-installer" \ + "https://api.github.com/repos/${REPO}/tags?per_page=100" 2>/dev/null \ + | tr -d '\r' \ + | grep -oE '"name":"[^"]+"' \ + | sed -E 's/^"name":"(.*)"$/\1/' \ + || true + )" + if [[ -n "$api_tags" ]]; then + latest_tag="$(printf '%s\n' "$api_tags" | pick_latest_plugin_tag)" + fi + + if [[ -z "$latest_tag" ]] && command -v git >/dev/null 2>&1; then + local git_tags="" + git_tags="$( + git ls-remote --tags --refs "https://github.com/${REPO}.git" 2>/dev/null \ + | sed -E 's#^.*refs/tags/##' \ + || true + )" + if [[ -n "$git_tags" ]]; then + latest_tag="$(printf '%s\n' "$git_tags" | pick_latest_plugin_tag)" + fi + fi + + if [[ -z "$latest_tag" ]]; then + err "$(tr "Could not resolve the latest tag for ${REPO}." "无法解析 ${REPO} 的最新 tag。")" + echo "$(tr "Please rerun with --plugin-version , or use --plugin-version main to track the branch head explicitly." "请使用 --plugin-version 重新执行;如果需要显式跟踪分支头,请使用 --plugin-version main。")" + exit 1 + fi + + PLUGIN_VERSION="$latest_tag" + info "$(tr "Resolved default plugin version to latest tag: ${PLUGIN_VERSION}" "已将默认插件版本解析为最新 tag: ${PLUGIN_VERSION}")" +} + # Detect OpenClaw version detect_openclaw_version() { local version_output @@ -1312,7 +1384,7 @@ check_openclaw_compatibility() { fi # If user explicitly requested an old version, pass - if [[ "$PLUGIN_VERSION" != "main" ]] && is_semver_like "$PLUGIN_VERSION" && ! version_gte "$PLUGIN_VERSION" "v0.2.8"; then + if is_semver_like "$PLUGIN_VERSION" && ! version_gte "$PLUGIN_VERSION" "v0.2.8"; then return 0 fi @@ -2020,20 +2092,22 @@ main() { detect_os ensure_plugin_only_operation_args select_workdir - info "Target: ${OPENCLAW_DIR}" - info "Repository: ${REPO}" - info "Plugin version: ${PLUGIN_VERSION}" - [[ -n "$OPENVIKING_VERSION" ]] && info "OpenViking version: ${OPENVIKING_VERSION}" - if [[ "$ROLLBACK_LAST_UPGRADE" == "1" ]]; then + info "Target: ${OPENCLAW_DIR}" + info "Repository: ${REPO}" info "Mode: rollback last plugin upgrade" - if [[ "${PLUGIN_VERSION}" != "main" ]]; then + if [[ "$PLUGIN_VERSION_EXPLICIT" == "1" ]]; then warn "--plugin-version is ignored in --rollback mode." fi rollback_last_upgrade_operation return 0 fi + resolve_default_plugin_version validate_requested_plugin_version + info "Target: ${OPENCLAW_DIR}" + info "Repository: ${REPO}" + info "Plugin version: ${PLUGIN_VERSION}" + [[ -n "$OPENVIKING_VERSION" ]] && info "OpenViking version: ${OPENVIKING_VERSION}" if [[ "$UPGRADE_PLUGIN_ONLY" == "1" ]]; then SELECTED_MODE="local" diff --git a/examples/openclaw-plugin/setup-helper/install.js b/examples/openclaw-plugin/setup-helper/install.js index 02924e466..16f13fda8 100644 --- a/examples/openclaw-plugin/setup-helper/install.js +++ b/examples/openclaw-plugin/setup-helper/install.js @@ -32,8 +32,10 @@ import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); let REPO = process.env.REPO || "volcengine/OpenViking"; -// PLUGIN_VERSION takes precedence over BRANCH (legacy) -let PLUGIN_VERSION = process.env.PLUGIN_VERSION || process.env.BRANCH || "main"; +// PLUGIN_VERSION takes precedence over BRANCH (legacy). If omitted, resolve the latest tag from GitHub. +const pluginVersionEnv = (process.env.PLUGIN_VERSION || process.env.BRANCH || "").trim(); +let PLUGIN_VERSION = pluginVersionEnv; +let pluginVersionExplicit = Boolean(pluginVersionEnv); const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com"; const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://mirrors.volces.com/pypi/simple/"; @@ -149,7 +151,13 @@ for (let i = 0; i < argv.length; i++) { continue; } if (arg.startsWith("--plugin-version=")) { - PLUGIN_VERSION = arg.slice("--plugin-version=".length).trim(); + const version = arg.slice("--plugin-version=".length).trim(); + if (!version) { + console.error("--plugin-version requires a value"); + process.exit(1); + } + PLUGIN_VERSION = version; + pluginVersionExplicit = true; continue; } if (arg === "--plugin-version") { @@ -159,6 +167,7 @@ for (let i = 0; i < argv.length; i++) { process.exit(1); } PLUGIN_VERSION = version; + pluginVersionExplicit = true; i += 1; continue; } @@ -209,7 +218,7 @@ function printHelp() { console.log(""); console.log("Options:"); console.log(" --github-repo=OWNER/REPO GitHub repository (default: volcengine/OpenViking)"); - console.log(" --plugin-version=TAG Plugin version (Git tag, e.g. v0.2.9, default: main)"); + console.log(" --plugin-version=TAG Plugin version (Git tag, e.g. v0.2.9, default: latest tag)"); console.log(" --openviking-version=V OpenViking PyPI version (e.g. 0.2.9, default: latest)"); console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)"); console.log(" --repo=PATH Use local OpenViking repo at PATH (pip -e + local plugin)"); @@ -231,7 +240,7 @@ function printHelp() { console.log(" # Install specific plugin version"); console.log(" node install.js --plugin-version=v0.2.8"); console.log(""); - console.log(" # Upgrade only the plugin files"); + console.log(" # Upgrade only the plugin files from main branch"); console.log(" node install.js --update --plugin-version=main"); console.log(""); console.log(" # Roll back the last plugin upgrade"); @@ -618,6 +627,120 @@ async function testRemoteFile(url) { return false; } +function compareSemverDesc(a, b) { + if (versionGte(a, b) && versionGte(b, a)) { + return 0; + } + return versionGte(a, b) ? -1 : 1; +} + +function pickLatestPluginTag(tagNames) { + const normalized = tagNames + .map((tag) => String(tag ?? "").trim()) + .filter(Boolean); + + const semverTags = normalized + .filter((tag) => isSemverLike(tag)) + .sort(compareSemverDesc); + + if (semverTags.length > 0) { + return semverTags[0]; + } + + return normalized[0] || ""; +} + +function parseGitLsRemoteTags(output) { + return String(output ?? "") + .split(/\r?\n/) + .map((line) => { + const match = line.match(/refs\/tags\/(.+)$/); + return match?.[1]?.trim() || ""; + }) + .filter(Boolean); +} + +async function resolveDefaultPluginVersion() { + if (PLUGIN_VERSION) { + return; + } + + info(tr( + `No plugin version specified; resolving latest tag from ${REPO}...`, + `未指定插件版本,正在解析 ${REPO} 的最新 tag...`, + )); + + const failures = []; + const apiUrl = `https://api.github.com/repos/${REPO}/tags?per_page=100`; + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); + const response = await fetch(apiUrl, { + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "openviking-setup-helper", + "X-GitHub-Api-Version": "2022-11-28", + }, + signal: controller.signal, + }); + clearTimeout(timeoutId); + + if (response.ok) { + const payload = await response.json().catch(() => null); + if (Array.isArray(payload)) { + const latestTag = pickLatestPluginTag(payload.map((item) => item?.name || "")); + if (latestTag) { + PLUGIN_VERSION = latestTag; + info(tr( + `Resolved default plugin version to latest tag: ${PLUGIN_VERSION}`, + `已将默认插件版本解析为最新 tag: ${PLUGIN_VERSION}`, + )); + return; + } + } else { + failures.push("GitHub tags API returned an unexpected payload"); + } + } else { + failures.push(`GitHub tags API returned HTTP ${response.status}`); + } + } catch (error) { + failures.push(`GitHub tags API failed: ${String(error)}`); + } + + const gitRef = `https://github.com/${REPO}.git`; + const gitResult = await runCapture("git", ["ls-remote", "--tags", "--refs", gitRef], { + shell: IS_WIN, + }); + if (gitResult.code === 0 && gitResult.out) { + const latestTag = pickLatestPluginTag(parseGitLsRemoteTags(gitResult.out)); + if (latestTag) { + PLUGIN_VERSION = latestTag; + info(tr( + `Resolved default plugin version via git tags: ${PLUGIN_VERSION}`, + `已通过 git tag 解析默认插件版本: ${PLUGIN_VERSION}`, + )); + return; + } + failures.push("git ls-remote returned no usable tags"); + } else { + failures.push(`git ls-remote failed${gitResult.err ? `: ${gitResult.err}` : ""}`); + } + + err(tr( + `Could not resolve the latest tag for ${REPO}.`, + `无法解析 ${REPO} 的最新 tag。`, + )); + console.log(tr( + "Please rerun with --plugin-version , or use --plugin-version main to track the branch head explicitly.", + "请使用 --plugin-version 重新执行;如果需要显式跟踪分支头,请使用 --plugin-version main。", + )); + if (failures.length > 0) { + warn(failures.join(" | ")); + } + process.exit(1); +} + // Resolve plugin configuration from manifest or fallback async function resolvePluginConfig() { const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`; @@ -741,7 +864,7 @@ async function checkOpenClawCompatibility() { } // If user explicitly requested an old version, pass - if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.8")) { + if (isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.8")) { return; } @@ -1949,12 +2072,13 @@ async function main() { await selectWorkdir(); if (rollbackLastUpgrade) { info(tr("Mode: rollback last plugin upgrade", "模式: 回滚最近一次插件升级")); - if (PLUGIN_VERSION !== "main") { + if (pluginVersionExplicit) { warn("--plugin-version is ignored in --rollback mode."); } await rollbackLastUpgradeOperation(); return; } + await resolveDefaultPluginVersion(); validateRequestedPluginVersion(); info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`)); info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`)); diff --git a/examples/openclaw-plugin/setup-helper/package.json b/examples/openclaw-plugin/setup-helper/package.json index b3666e8f4..f500ed1fa 100644 --- a/examples/openclaw-plugin/setup-helper/package.json +++ b/examples/openclaw-plugin/setup-helper/package.json @@ -1,6 +1,6 @@ { "name": "openclaw-openviking-setup-helper", - "version": "0.2.10", + "version": "0.2.11", "description": "Setup helper for installing OpenViking memory plugin into OpenClaw", "type": "module", "bin": {