diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d769988c21..7aff77a55ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/scripts/postinstall-bundled-plugins.mjs b/scripts/postinstall-bundled-plugins.mjs index ff8a13c05f0..ac00cfb6083 100644 --- a/scripts/postinstall-bundled-plugins.mjs +++ b/scripts/postinstall-bundled-plugins.mjs @@ -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, }); diff --git a/src/commands/doctor/shared/plugin-runtime-symlinks.ts b/src/commands/doctor/shared/plugin-runtime-symlinks.ts index 323d8bde9a4..505e283aa94 100644 --- a/src/commands/doctor/shared/plugin-runtime-symlinks.ts +++ b/src/commands/doctor/shared/plugin-runtime-symlinks.ts @@ -12,6 +12,7 @@ interface FsLike { readlink(file: string): Promise; stat(file: string): Promise; rm(file: string, options: { force: true }): Promise; + unlink?(file: string): Promise; } 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)}`);