fix(upgrade): unlink stale plugin runtime symlinks

This commit is contained in:
Vincent Koc
2026-05-03 14:04:26 -07:00
parent c33e578554
commit 990f931a2e
3 changed files with 13 additions and 3 deletions

View File

@@ -38,7 +38,7 @@ Docs: https://docs.openclaw.ai
- Agents/network: allow trusted web-search providers and configured model-provider hosts to work behind Surge/Clash/sing-box fake-IP DNS by accepting RFC 2544 and IPv6 ULA synthetic answers only for the request's scoped hostname, without broad private-network access. Refs #76530 and #76549. Thanks @zqchris.
- Providers: honor env-proxy settings for guarded provider model fetches when no explicit dispatcher policy is configured, preserving explicit transport overrides. Fixes #70453. (#72480) Thanks @mjamiv.
- Feishu: accept and honor `channels.feishu.blockStreaming` at the top level and per account, while keeping the legacy default off so Feishu cards no longer reject documented config or silently drop block replies. Fixes #75555. Thanks @vincentkoc.
- Gateway/update: avoid `launchctl kickstart -k` immediately after fresh macOS update bootstraps, and remove dangling global plugin-runtime symlinks during packaged postinstall and `doctor --fix` so upgrades no longer SIGTERM the newly booted Gateway or leave bundled plugin imports pointed at pruned `plugin-runtime-deps` trees. Completes #76261 and fixes #76466. (#76929)
- Gateway/update: avoid `launchctl kickstart -k` immediately after fresh macOS update bootstraps, and unlink dangling global plugin-runtime symlinks during packaged postinstall and `doctor --fix` so upgrades no longer SIGTERM the newly booted Gateway or leave bundled plugin imports pointed at pruned `plugin-runtime-deps` trees. Completes #76261 and fixes #76466. (#76929)
- Google Chat: normalize custom Google auth transport headers before google-auth/gaxios interceptors run, restoring webhook token verification when certificate retrieval expects Fetch `Headers`. Fixes #76742. Thanks @donbowman.
- Doctor/plugins: reset stale `plugins.slots.memory` and `plugins.slots.contextEngine` references during `doctor --fix`, so cleanup of missing plugin config does not leave unrecoverable slot owners behind. Fixes #76550 and #76551. Thanks @vincentkoc.
- Docs/WhatsApp: merge the duplicate top-level `web` objects in the gateway channel config example so copy-pasted WhatsApp config keeps both `web.whatsapp` and reconnect settings. Fixes #76619. Thanks @WadydX.

View File

@@ -445,6 +445,7 @@ function collectLegacyPluginRuntimeDepsSymlinkPaths(roots, params = {}) {
export function pruneLegacyPluginRuntimeDepsState(params = {}) {
const pathExists = params.existsSync ?? existsSync;
const removePath = params.rmSync ?? rmSync;
const unlinkPath = params.unlinkSync ?? unlinkSync;
const log = params.log ?? console;
const removed = [];
const removedSymlinks = [];
@@ -452,7 +453,7 @@ export function pruneLegacyPluginRuntimeDepsState(params = {}) {
for (const linkPath of collectLegacyPluginRuntimeDepsSymlinkPaths(roots, params)) {
try {
removePath(linkPath, { force: true });
unlinkPath(linkPath);
removedSymlinks.push(linkPath);
} catch (error) {
log.warn?.(
@@ -889,7 +890,10 @@ export function runBundledPluginPostinstall(params = {}) {
env,
packageRoot,
existsSync: pathExists,
lstatSync: params.lstatSync,
readlinkSync: params.readlinkSync,
rmSync: params.rmSync,
unlinkSync: params.unlinkSync,
log,
homedir: params.homedir,
});

View File

@@ -12,6 +12,7 @@ interface FsLike {
readlink(file: string): Promise<string>;
stat(file: string): Promise<unknown>;
rm(file: string, options: { force: true }): Promise<void>;
unlink?(file: string): Promise<void>;
}
interface DirentLike {
@@ -41,6 +42,7 @@ const DEFAULT_FS: FsLike = {
readlink: (file) => fs.readlink(file),
stat: (file) => fs.stat(file),
rm: (file, options) => fs.rm(file, options),
unlink: (file) => fs.unlink(file),
};
export async function collectStalePluginRuntimeSymlinks(
@@ -126,7 +128,11 @@ export async function removeStalePluginRuntimeSymlinks(
const warnings: string[] = [];
for (const item of await collectStalePluginRuntimeSymlinks(packageRoot, options)) {
try {
await fsApi.rm(item.path, { force: true });
if (fsApi.unlink) {
await fsApi.unlink(item.path);
} else {
await fsApi.rm(item.path, { force: true });
}
changes.push(`Removed stale plugin-runtime symlink: ${item.path}`);
} catch (error) {
warnings.push(`Failed to remove stale plugin-runtime symlink ${item.path}: ${String(error)}`);