UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

1,221 lines (1,216 loc) 32.5 kB
import { t as runCommandWithTimeout } from "./exec-CTo4hK94.js"; import { o as trimLogTail } from "./restart-sentinel-CdcBcziq.js"; import { r as parseSemver } from "./runtime-guard-BG6JybtL.js"; import os from "node:os"; import path from "node:path"; import fs from "node:fs/promises"; //#region src/infra/update-channels.ts const DEFAULT_PACKAGE_CHANNEL = "stable"; const DEFAULT_GIT_CHANNEL = "dev"; const DEV_BRANCH = "main"; function normalizeUpdateChannel(value) { if (!value) return null; const normalized = value.trim().toLowerCase(); if (normalized === "stable" || normalized === "beta" || normalized === "dev") return normalized; return null; } function channelToNpmTag(channel) { if (channel === "beta") return "beta"; if (channel === "dev") return "dev"; return "latest"; } function isBetaTag(tag) { return tag.toLowerCase().includes("-beta"); } function isStableTag(tag) { return !isBetaTag(tag); } function resolveEffectiveUpdateChannel(params) { if (params.configChannel) return { channel: params.configChannel, source: "config" }; if (params.installKind === "git") { const tag = params.git?.tag; if (tag) return { channel: isBetaTag(tag) ? "beta" : "stable", source: "git-tag" }; const branch = params.git?.branch; if (branch && branch !== "HEAD") return { channel: "dev", source: "git-branch" }; return { channel: DEFAULT_GIT_CHANNEL, source: "default" }; } if (params.installKind === "package") return { channel: DEFAULT_PACKAGE_CHANNEL, source: "default" }; return { channel: DEFAULT_PACKAGE_CHANNEL, source: "default" }; } function formatUpdateChannelLabel(params) { if (params.source === "config") return `${params.channel} (config)`; if (params.source === "git-tag") return params.gitTag ? `${params.channel} (${params.gitTag})` : `${params.channel} (tag)`; if (params.source === "git-branch") return params.gitBranch ? `${params.channel} (${params.gitBranch})` : `${params.channel} (branch)`; return `${params.channel} (default)`; } //#endregion //#region src/infra/update-check.ts async function exists(p) { try { await fs.access(p); return true; } catch { return false; } } async function detectPackageManager$1(root) { try { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const pm = JSON.parse(raw)?.packageManager?.split("@")[0]?.trim(); if (pm === "pnpm" || pm === "bun" || pm === "npm") return pm; } catch {} const files = await fs.readdir(root).catch(() => []); if (files.includes("pnpm-lock.yaml")) return "pnpm"; if (files.includes("bun.lockb")) return "bun"; if (files.includes("package-lock.json")) return "npm"; return "unknown"; } async function detectGitRoot(root) { const res = await runCommandWithTimeout([ "git", "-C", root, "rev-parse", "--show-toplevel" ], { timeoutMs: 4e3 }).catch(() => null); if (!res || res.code !== 0) return null; const top = res.stdout.trim(); return top ? path.resolve(top) : null; } async function checkGitUpdateStatus(params) { const timeoutMs = params.timeoutMs ?? 6e3; const root = path.resolve(params.root); const base = { root, sha: null, tag: null, branch: null, upstream: null, dirty: null, ahead: null, behind: null, fetchOk: null }; const branchRes = await runCommandWithTimeout([ "git", "-C", root, "rev-parse", "--abbrev-ref", "HEAD" ], { timeoutMs }).catch(() => null); if (!branchRes || branchRes.code !== 0) return { ...base, error: branchRes?.stderr?.trim() || "git unavailable" }; const branch = branchRes.stdout.trim() || null; const shaRes = await runCommandWithTimeout([ "git", "-C", root, "rev-parse", "HEAD" ], { timeoutMs }).catch(() => null); const sha = shaRes && shaRes.code === 0 ? shaRes.stdout.trim() : null; const tagRes = await runCommandWithTimeout([ "git", "-C", root, "describe", "--tags", "--exact-match" ], { timeoutMs }).catch(() => null); const tag = tagRes && tagRes.code === 0 ? tagRes.stdout.trim() : null; const upstreamRes = await runCommandWithTimeout([ "git", "-C", root, "rev-parse", "--abbrev-ref", "@{upstream}" ], { timeoutMs }).catch(() => null); const upstream = upstreamRes && upstreamRes.code === 0 ? upstreamRes.stdout.trim() : null; const dirtyRes = await runCommandWithTimeout([ "git", "-C", root, "status", "--porcelain", "--", ":!dist/control-ui/" ], { timeoutMs }).catch(() => null); const dirty = dirtyRes && dirtyRes.code === 0 ? dirtyRes.stdout.trim().length > 0 : null; const fetchOk = params.fetch ? await runCommandWithTimeout([ "git", "-C", root, "fetch", "--quiet", "--prune" ], { timeoutMs }).then((r) => r.code === 0).catch(() => false) : null; const counts = upstream && upstream.length > 0 ? await runCommandWithTimeout([ "git", "-C", root, "rev-list", "--left-right", "--count", `HEAD...${upstream}` ], { timeoutMs }).catch(() => null) : null; const parseCounts = (raw) => { const parts = raw.trim().split(/\s+/); if (parts.length < 2) return null; const ahead = Number.parseInt(parts[0] ?? "", 10); const behind = Number.parseInt(parts[1] ?? "", 10); if (!Number.isFinite(ahead) || !Number.isFinite(behind)) return null; return { ahead, behind }; }; const parsed = counts && counts.code === 0 ? parseCounts(counts.stdout) : null; return { root, sha, tag, branch, upstream, dirty, ahead: parsed?.ahead ?? null, behind: parsed?.behind ?? null, fetchOk }; } async function statMtimeMs(p) { try { return (await fs.stat(p)).mtimeMs; } catch { return null; } } function resolveDepsMarker(params) { const root = params.root; if (params.manager === "pnpm") return { lockfilePath: path.join(root, "pnpm-lock.yaml"), markerPath: path.join(root, "node_modules", ".modules.yaml") }; if (params.manager === "bun") return { lockfilePath: path.join(root, "bun.lockb"), markerPath: path.join(root, "node_modules") }; if (params.manager === "npm") return { lockfilePath: path.join(root, "package-lock.json"), markerPath: path.join(root, "node_modules") }; return { lockfilePath: null, markerPath: null }; } async function checkDepsStatus(params) { const { lockfilePath, markerPath } = resolveDepsMarker({ root: path.resolve(params.root), manager: params.manager }); if (!lockfilePath || !markerPath) return { manager: params.manager, status: "unknown", lockfilePath, markerPath, reason: "unknown package manager" }; const lockExists = await exists(lockfilePath); const markerExists = await exists(markerPath); if (!lockExists) return { manager: params.manager, status: "unknown", lockfilePath, markerPath, reason: "lockfile missing" }; if (!markerExists) return { manager: params.manager, status: "missing", lockfilePath, markerPath, reason: "node_modules marker missing" }; const lockMtime = await statMtimeMs(lockfilePath); const markerMtime = await statMtimeMs(markerPath); if (!lockMtime || !markerMtime) return { manager: params.manager, status: "unknown", lockfilePath, markerPath }; if (lockMtime > markerMtime + 1e3) return { manager: params.manager, status: "stale", lockfilePath, markerPath, reason: "lockfile newer than install marker" }; return { manager: params.manager, status: "ok", lockfilePath, markerPath }; } async function fetchWithTimeout(url, timeoutMs) { const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), Math.max(250, timeoutMs)); try { return await fetch(url, { signal: ctrl.signal }); } finally { clearTimeout(t); } } async function fetchNpmLatestVersion(params) { const res = await fetchNpmTagVersion({ tag: "latest", timeoutMs: params?.timeoutMs }); return { latestVersion: res.version, error: res.error }; } async function fetchNpmTagVersion(params) { const timeoutMs = params?.timeoutMs ?? 3500; const tag = params.tag; try { const res = await fetchWithTimeout(`https://registry.npmjs.org/openclaw/${encodeURIComponent(tag)}`, timeoutMs); if (!res.ok) return { tag, version: null, error: `HTTP ${res.status}` }; const json = await res.json(); return { tag, version: typeof json?.version === "string" ? json.version : null }; } catch (err) { return { tag, version: null, error: String(err) }; } } async function resolveNpmChannelTag(params) { const channelTag = channelToNpmTag(params.channel); const channelStatus = await fetchNpmTagVersion({ tag: channelTag, timeoutMs: params.timeoutMs }); if (params.channel !== "beta") return { tag: channelTag, version: channelStatus.version }; const latestStatus = await fetchNpmTagVersion({ tag: "latest", timeoutMs: params.timeoutMs }); if (!latestStatus.version) return { tag: channelTag, version: channelStatus.version }; if (!channelStatus.version) return { tag: "latest", version: latestStatus.version }; const cmp = compareSemverStrings(channelStatus.version, latestStatus.version); if (cmp != null && cmp < 0) return { tag: "latest", version: latestStatus.version }; return { tag: channelTag, version: channelStatus.version }; } function compareSemverStrings(a, b) { const pa = parseSemver(a); const pb = parseSemver(b); if (!pa || !pb) return null; if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1; if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1; if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1; return 0; } async function checkUpdateStatus(params) { const timeoutMs = params.timeoutMs ?? 6e3; const root = params.root ? path.resolve(params.root) : null; if (!root) return { root: null, installKind: "unknown", packageManager: "unknown", registry: params.includeRegistry ? await fetchNpmLatestVersion({ timeoutMs }) : void 0 }; const pm = await detectPackageManager$1(root); const gitRoot = await detectGitRoot(root); const isGit = gitRoot && path.resolve(gitRoot) === root; return { root, installKind: isGit ? "git" : "package", packageManager: pm, git: isGit ? await checkGitUpdateStatus({ root, timeoutMs, fetch: Boolean(params.fetchGit) }) : void 0, deps: await checkDepsStatus({ root, manager: pm }), registry: params.includeRegistry ? await fetchNpmLatestVersion({ timeoutMs }) : void 0 }; } //#endregion //#region src/infra/update-global.ts const PRIMARY_PACKAGE_NAME = "openclaw"; const ALL_PACKAGE_NAMES = [PRIMARY_PACKAGE_NAME]; const GLOBAL_RENAME_PREFIX = "."; async function pathExists(targetPath) { try { await fs.access(targetPath); return true; } catch { return false; } } async function tryRealpath(targetPath) { try { return await fs.realpath(targetPath); } catch { return path.resolve(targetPath); } } function resolveBunGlobalRoot() { const bunInstall = process.env.BUN_INSTALL?.trim() || path.join(os.homedir(), ".bun"); return path.join(bunInstall, "install", "global", "node_modules"); } async function resolveGlobalRoot(manager, runCommand, timeoutMs) { if (manager === "bun") return resolveBunGlobalRoot(); const res = await runCommand(manager === "pnpm" ? [ "pnpm", "root", "-g" ] : [ "npm", "root", "-g" ], { timeoutMs }).catch(() => null); if (!res || res.code !== 0) return null; return res.stdout.trim() || null; } async function resolveGlobalPackageRoot(manager, runCommand, timeoutMs) { const root = await resolveGlobalRoot(manager, runCommand, timeoutMs); if (!root) return null; return path.join(root, PRIMARY_PACKAGE_NAME); } async function detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs) { const pkgReal = await tryRealpath(pkgRoot); for (const { manager, argv } of [{ manager: "npm", argv: [ "npm", "root", "-g" ] }, { manager: "pnpm", argv: [ "pnpm", "root", "-g" ] }]) { const res = await runCommand(argv, { timeoutMs }).catch(() => null); if (!res || res.code !== 0) continue; const globalRoot = res.stdout.trim(); if (!globalRoot) continue; const globalReal = await tryRealpath(globalRoot); for (const name of ALL_PACKAGE_NAMES) { const expected = path.join(globalReal, name); if (path.resolve(expected) === path.resolve(pkgReal)) return manager; } } const bunGlobalReal = await tryRealpath(resolveBunGlobalRoot()); for (const name of ALL_PACKAGE_NAMES) { const bunExpected = path.join(bunGlobalReal, name); if (path.resolve(bunExpected) === path.resolve(pkgReal)) return "bun"; } return null; } async function detectGlobalInstallManagerByPresence(runCommand, timeoutMs) { for (const manager of ["npm", "pnpm"]) { const root = await resolveGlobalRoot(manager, runCommand, timeoutMs); if (!root) continue; for (const name of ALL_PACKAGE_NAMES) if (await pathExists(path.join(root, name))) return manager; } const bunRoot = resolveBunGlobalRoot(); for (const name of ALL_PACKAGE_NAMES) if (await pathExists(path.join(bunRoot, name))) return "bun"; return null; } function globalInstallArgs(manager, spec) { if (manager === "pnpm") return [ "pnpm", "add", "-g", spec ]; if (manager === "bun") return [ "bun", "add", "-g", spec ]; return [ "npm", "i", "-g", spec ]; } async function cleanupGlobalRenameDirs(params) { const removed = []; const root = params.globalRoot.trim(); const name = params.packageName.trim(); if (!root || !name) return { removed }; const prefix = `${GLOBAL_RENAME_PREFIX}${name}-`; let entries = []; try { entries = await fs.readdir(root); } catch { return { removed }; } for (const entry of entries) { if (!entry.startsWith(prefix)) continue; const target = path.join(root, entry); try { if (!(await fs.lstat(target)).isDirectory()) continue; await fs.rm(target, { recursive: true, force: true }); removed.push(entry); } catch {} } return { removed }; } //#endregion //#region src/infra/update-runner.ts const DEFAULT_TIMEOUT_MS = 20 * 6e4; const MAX_LOG_CHARS = 8e3; const PREFLIGHT_MAX_COMMITS = 10; const START_DIRS = [ "cwd", "argv1", "process" ]; const DEFAULT_PACKAGE_NAME = "openclaw"; const CORE_PACKAGE_NAMES = new Set([DEFAULT_PACKAGE_NAME]); function normalizeDir(value) { if (!value) return null; const trimmed = value.trim(); if (!trimmed) return null; return path.resolve(trimmed); } function resolveNodeModulesBinPackageRoot(argv1) { const normalized = path.resolve(argv1); const parts = normalized.split(path.sep); const binIndex = parts.lastIndexOf(".bin"); if (binIndex <= 0) return null; if (parts[binIndex - 1] !== "node_modules") return null; const binName = path.basename(normalized); const nodeModulesDir = parts.slice(0, binIndex).join(path.sep); return path.join(nodeModulesDir, binName); } function buildStartDirs(opts) { const dirs = []; const cwd = normalizeDir(opts.cwd); if (cwd) dirs.push(cwd); const argv1 = normalizeDir(opts.argv1); if (argv1) { dirs.push(path.dirname(argv1)); const packageRoot = resolveNodeModulesBinPackageRoot(argv1); if (packageRoot) dirs.push(packageRoot); } const proc = normalizeDir(process.cwd()); if (proc) dirs.push(proc); return Array.from(new Set(dirs)); } async function readPackageVersion(root) { try { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const parsed = JSON.parse(raw); return typeof parsed?.version === "string" ? parsed.version : null; } catch { return null; } } async function readPackageName(root) { try { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const name = JSON.parse(raw)?.name?.trim(); return name ? name : null; } catch { return null; } } async function readBranchName(runCommand, root, timeoutMs) { const res = await runCommand([ "git", "-C", root, "rev-parse", "--abbrev-ref", "HEAD" ], { timeoutMs }).catch(() => null); if (!res || res.code !== 0) return null; return res.stdout.trim() || null; } async function listGitTags(runCommand, root, timeoutMs, pattern = "v*") { const res = await runCommand([ "git", "-C", root, "tag", "--list", pattern, "--sort=-v:refname" ], { timeoutMs }).catch(() => null); if (!res || res.code !== 0) return []; return res.stdout.split("\n").map((line) => line.trim()).filter(Boolean); } async function resolveChannelTag(runCommand, root, timeoutMs, channel) { const tags = await listGitTags(runCommand, root, timeoutMs); if (channel === "beta") { const betaTag = tags.find((tag) => isBetaTag(tag)) ?? null; const stableTag = tags.find((tag) => isStableTag(tag)) ?? null; if (!betaTag) return stableTag; if (!stableTag) return betaTag; const cmp = compareSemverStrings(betaTag, stableTag); if (cmp != null && cmp < 0) return stableTag; return betaTag; } return tags.find((tag) => isStableTag(tag)) ?? null; } async function resolveGitRoot(runCommand, candidates, timeoutMs) { for (const dir of candidates) { const res = await runCommand([ "git", "-C", dir, "rev-parse", "--show-toplevel" ], { timeoutMs }); if (res.code === 0) { const root = res.stdout.trim(); if (root) return root; } } return null; } async function findPackageRoot(candidates) { for (const dir of candidates) { let current = dir; for (let i = 0; i < 12; i += 1) { const pkgPath = path.join(current, "package.json"); try { const raw = await fs.readFile(pkgPath, "utf-8"); const name = JSON.parse(raw)?.name?.trim(); if (name && CORE_PACKAGE_NAMES.has(name)) return current; } catch {} const parent = path.dirname(current); if (parent === current) break; current = parent; } } return null; } async function detectPackageManager(root) { try { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const pm = JSON.parse(raw)?.packageManager?.split("@")[0]?.trim(); if (pm === "pnpm" || pm === "bun" || pm === "npm") return pm; } catch {} const files = await fs.readdir(root).catch(() => []); if (files.includes("pnpm-lock.yaml")) return "pnpm"; if (files.includes("bun.lockb")) return "bun"; if (files.includes("package-lock.json")) return "npm"; return "npm"; } async function runStep(opts) { const { runCommand, name, argv, cwd, timeoutMs, env, progress, stepIndex, totalSteps } = opts; const command = argv.join(" "); const stepInfo = { name, command, index: stepIndex, total: totalSteps }; progress?.onStepStart?.(stepInfo); const started = Date.now(); const result = await runCommand(argv, { cwd, timeoutMs, env }); const durationMs = Date.now() - started; const stderrTail = trimLogTail(result.stderr, MAX_LOG_CHARS); progress?.onStepComplete?.({ ...stepInfo, durationMs, exitCode: result.code, stderrTail }); return { name, command, cwd, durationMs, exitCode: result.code, stdoutTail: trimLogTail(result.stdout, MAX_LOG_CHARS), stderrTail: trimLogTail(result.stderr, MAX_LOG_CHARS) }; } function managerScriptArgs(manager, script, args = []) { if (manager === "pnpm") return [ "pnpm", script, ...args ]; if (manager === "bun") return [ "bun", "run", script, ...args ]; if (args.length > 0) return [ "npm", "run", script, "--", ...args ]; return [ "npm", "run", script ]; } function managerInstallArgs(manager) { if (manager === "pnpm") return ["pnpm", "install"]; if (manager === "bun") return ["bun", "install"]; return ["npm", "install"]; } function normalizeTag(tag) { const trimmed = tag?.trim(); if (!trimmed) return "latest"; if (trimmed.startsWith("openclaw@")) return trimmed.slice(9); if (trimmed.startsWith(`${DEFAULT_PACKAGE_NAME}@`)) return trimmed.slice(`${DEFAULT_PACKAGE_NAME}@`.length); return trimmed; } async function runGatewayUpdate(opts = {}) { const startedAt = Date.now(); const runCommand = opts.runCommand ?? (async (argv, options) => { const res = await runCommandWithTimeout(argv, options); return { stdout: res.stdout, stderr: res.stderr, code: res.code }; }); const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS; const progress = opts.progress; const steps = []; const candidates = buildStartDirs(opts); let stepIndex = 0; let gitTotalSteps = 0; const step = (name, argv, cwd, env) => { const currentIndex = stepIndex; stepIndex += 1; return { runCommand, name, argv, cwd, timeoutMs, env, progress, stepIndex: currentIndex, totalSteps: gitTotalSteps }; }; const pkgRoot = await findPackageRoot(candidates); let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs); if (gitRoot && pkgRoot && path.resolve(gitRoot) !== path.resolve(pkgRoot)) gitRoot = null; if (gitRoot && !pkgRoot) return { status: "error", mode: "unknown", root: gitRoot, reason: "not-openclaw-root", steps: [], durationMs: Date.now() - startedAt }; if (gitRoot && pkgRoot && path.resolve(gitRoot) === path.resolve(pkgRoot)) { const beforeSha = (await runCommand([ "git", "-C", gitRoot, "rev-parse", "HEAD" ], { cwd: gitRoot, timeoutMs })).stdout.trim() || null; const beforeVersion = await readPackageVersion(gitRoot); const channel = opts.channel ?? "dev"; const branch = channel === "dev" ? await readBranchName(runCommand, gitRoot, timeoutMs) : null; const needsCheckoutMain = channel === "dev" && branch !== DEV_BRANCH; gitTotalSteps = channel === "dev" ? needsCheckoutMain ? 11 : 10 : 9; const statusCheck = await runStep(step("clean check", [ "git", "-C", gitRoot, "status", "--porcelain", "--", ":!dist/control-ui/" ], gitRoot)); steps.push(statusCheck); if (statusCheck.stdoutTail && statusCheck.stdoutTail.trim().length > 0) return { status: "skipped", mode: "git", root: gitRoot, reason: "dirty", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; if (channel === "dev") { if (needsCheckoutMain) { const checkoutStep = await runStep(step(`git checkout ${DEV_BRANCH}`, [ "git", "-C", gitRoot, "checkout", DEV_BRANCH ], gitRoot)); steps.push(checkoutStep); if (checkoutStep.exitCode !== 0) return { status: "error", mode: "git", root: gitRoot, reason: "checkout-failed", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; } const upstreamStep = await runStep(step("upstream check", [ "git", "-C", gitRoot, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}" ], gitRoot)); steps.push(upstreamStep); if (upstreamStep.exitCode !== 0) return { status: "skipped", mode: "git", root: gitRoot, reason: "no-upstream", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const fetchStep = await runStep(step("git fetch", [ "git", "-C", gitRoot, "fetch", "--all", "--prune", "--tags" ], gitRoot)); steps.push(fetchStep); const upstreamShaStep = await runStep(step("git rev-parse @{upstream}", [ "git", "-C", gitRoot, "rev-parse", "@{upstream}" ], gitRoot)); steps.push(upstreamShaStep); const upstreamSha = upstreamShaStep.stdoutTail?.trim(); if (!upstreamShaStep.stdoutTail || !upstreamSha) return { status: "error", mode: "git", root: gitRoot, reason: "no-upstream-sha", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const revListStep = await runStep(step("git rev-list", [ "git", "-C", gitRoot, "rev-list", `--max-count=${PREFLIGHT_MAX_COMMITS}`, upstreamSha ], gitRoot)); steps.push(revListStep); if (revListStep.exitCode !== 0) return { status: "error", mode: "git", root: gitRoot, reason: "preflight-revlist-failed", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const candidates = (revListStep.stdoutTail ?? "").split("\n").map((line) => line.trim()).filter(Boolean); if (candidates.length === 0) return { status: "error", mode: "git", root: gitRoot, reason: "preflight-no-candidates", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const manager = await detectPackageManager(gitRoot); const preflightRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-update-preflight-")); const worktreeDir = path.join(preflightRoot, "worktree"); const worktreeStep = await runStep(step("preflight worktree", [ "git", "-C", gitRoot, "worktree", "add", "--detach", worktreeDir, upstreamSha ], gitRoot)); steps.push(worktreeStep); if (worktreeStep.exitCode !== 0) { await fs.rm(preflightRoot, { recursive: true, force: true }).catch(() => {}); return { status: "error", mode: "git", root: gitRoot, reason: "preflight-worktree-failed", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; } let selectedSha = null; try { for (const sha of candidates) { const shortSha = sha.slice(0, 8); const checkoutStep = await runStep(step(`preflight checkout (${shortSha})`, [ "git", "-C", worktreeDir, "checkout", "--detach", sha ], worktreeDir)); steps.push(checkoutStep); if (checkoutStep.exitCode !== 0) continue; const depsStep = await runStep(step(`preflight deps install (${shortSha})`, managerInstallArgs(manager), worktreeDir)); steps.push(depsStep); if (depsStep.exitCode !== 0) continue; const lintStep = await runStep(step(`preflight lint (${shortSha})`, managerScriptArgs(manager, "lint"), worktreeDir)); steps.push(lintStep); if (lintStep.exitCode !== 0) continue; const buildStep = await runStep(step(`preflight build (${shortSha})`, managerScriptArgs(manager, "build"), worktreeDir)); steps.push(buildStep); if (buildStep.exitCode !== 0) continue; selectedSha = sha; break; } } finally { const removeStep = await runStep(step("preflight cleanup", [ "git", "-C", gitRoot, "worktree", "remove", "--force", worktreeDir ], gitRoot)); steps.push(removeStep); await runCommand([ "git", "-C", gitRoot, "worktree", "prune" ], { cwd: gitRoot, timeoutMs }).catch(() => null); await fs.rm(preflightRoot, { recursive: true, force: true }).catch(() => {}); } if (!selectedSha) return { status: "error", mode: "git", root: gitRoot, reason: "preflight-no-good-commit", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const rebaseStep = await runStep(step("git rebase", [ "git", "-C", gitRoot, "rebase", selectedSha ], gitRoot)); steps.push(rebaseStep); if (rebaseStep.exitCode !== 0) { const abortResult = await runCommand([ "git", "-C", gitRoot, "rebase", "--abort" ], { cwd: gitRoot, timeoutMs }); steps.push({ name: "git rebase --abort", command: "git rebase --abort", cwd: gitRoot, durationMs: 0, exitCode: abortResult.code, stdoutTail: trimLogTail(abortResult.stdout, MAX_LOG_CHARS), stderrTail: trimLogTail(abortResult.stderr, MAX_LOG_CHARS) }); return { status: "error", mode: "git", root: gitRoot, reason: "rebase-failed", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; } } else { const fetchStep = await runStep(step("git fetch", [ "git", "-C", gitRoot, "fetch", "--all", "--prune", "--tags" ], gitRoot)); steps.push(fetchStep); if (fetchStep.exitCode !== 0) return { status: "error", mode: "git", root: gitRoot, reason: "fetch-failed", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const tag = await resolveChannelTag(runCommand, gitRoot, timeoutMs, channel); if (!tag) return { status: "error", mode: "git", root: gitRoot, reason: "no-release-tag", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; const checkoutStep = await runStep(step(`git checkout ${tag}`, [ "git", "-C", gitRoot, "checkout", "--detach", tag ], gitRoot)); steps.push(checkoutStep); if (checkoutStep.exitCode !== 0) return { status: "error", mode: "git", root: gitRoot, reason: "checkout-failed", before: { sha: beforeSha, version: beforeVersion }, steps, durationMs: Date.now() - startedAt }; } const manager = await detectPackageManager(gitRoot); const depsStep = await runStep(step("deps install", managerInstallArgs(manager), gitRoot)); steps.push(depsStep); const buildStep = await runStep(step("build", managerScriptArgs(manager, "build"), gitRoot)); steps.push(buildStep); const uiBuildStep = await runStep(step("ui:build", managerScriptArgs(manager, "ui:build"), gitRoot)); steps.push(uiBuildStep); const restoreUiStep = await runStep(step("restore control-ui", [ "git", "-C", gitRoot, "checkout", "--", "dist/control-ui/" ], gitRoot)); steps.push(restoreUiStep); const doctorStep = await runStep(step("openclaw doctor", managerScriptArgs(manager, "openclaw", ["doctor", "--non-interactive"]), gitRoot, { OPENCLAW_UPDATE_IN_PROGRESS: "1" })); steps.push(doctorStep); const failedStep = steps.find((s) => s.exitCode !== 0); const afterShaStep = await runStep(step("git rev-parse HEAD (after)", [ "git", "-C", gitRoot, "rev-parse", "HEAD" ], gitRoot)); steps.push(afterShaStep); const afterVersion = await readPackageVersion(gitRoot); return { status: failedStep ? "error" : "ok", mode: "git", root: gitRoot, reason: failedStep ? failedStep.name : void 0, before: { sha: beforeSha, version: beforeVersion }, after: { sha: afterShaStep.stdoutTail?.trim() ?? null, version: afterVersion }, steps, durationMs: Date.now() - startedAt }; } if (!pkgRoot) return { status: "error", mode: "unknown", reason: `no root (${START_DIRS.join(",")})`, steps: [], durationMs: Date.now() - startedAt }; const beforeVersion = await readPackageVersion(pkgRoot); const globalManager = await detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs); if (globalManager) { const packageName = await readPackageName(pkgRoot) ?? DEFAULT_PACKAGE_NAME; await cleanupGlobalRenameDirs({ globalRoot: path.dirname(pkgRoot), packageName }); const channel = opts.channel ?? DEFAULT_PACKAGE_CHANNEL; const updateStep = await runStep({ runCommand, name: "global update", argv: globalInstallArgs(globalManager, `${packageName}@${normalizeTag(opts.tag ?? channelToNpmTag(channel))}`), cwd: pkgRoot, timeoutMs, progress, stepIndex: 0, totalSteps: 1 }); const steps = [updateStep]; const afterVersion = await readPackageVersion(pkgRoot); return { status: updateStep.exitCode === 0 ? "ok" : "error", mode: globalManager, root: pkgRoot, reason: updateStep.exitCode === 0 ? void 0 : updateStep.name, before: { version: beforeVersion }, after: { version: afterVersion }, steps, durationMs: Date.now() - startedAt }; } return { status: "skipped", mode: "unknown", root: pkgRoot, reason: "not-git-install", before: { version: beforeVersion }, steps: [], durationMs: Date.now() - startedAt }; } //#endregion export { globalInstallArgs as a, compareSemverStrings as c, DEFAULT_GIT_CHANNEL as d, DEFAULT_PACKAGE_CHANNEL as f, resolveEffectiveUpdateChannel as g, normalizeUpdateChannel as h, detectGlobalInstallManagerForRoot as i, fetchNpmTagVersion as l, formatUpdateChannelLabel as m, cleanupGlobalRenameDirs as n, resolveGlobalPackageRoot as o, channelToNpmTag as p, detectGlobalInstallManagerByPresence as r, checkUpdateStatus as s, runGatewayUpdate as t, resolveNpmChannelTag as u };