From d62cc59388b548f7cca2802cdd8db66349314352 Mon Sep 17 00:00:00 2001 From: Shakker Date: Mon, 27 Apr 2026 16:20:58 +0100 Subject: [PATCH] fix: reuse startup metadata for auto enable --- CHANGELOG.md | 1 + .../server-startup-config.recovery.test.ts | 59 +++++++++++++++++++ src/gateway/server-startup-config.ts | 8 ++- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b1aa919d9a..2202feb0362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai - Gateway/device tokens: stop echoing rotated bearer tokens from shared/admin `device.token.rotate` responses while preserving the same-device token handoff needed by token-only clients before reconnect. (#66773) Thanks @MoerAI. - Control UI/Talk: keep Google Live browser sessions on the WebSocket transport instead of falling back to WebRTC, validate browser Google Live WebSocket endpoints, cap Gateway relay sessions per browser connection, and remove stale browser-native voice buttons that did not use the configured Talk/TTS provider. Thanks @BunsDev. +- Gateway/startup: reuse config snapshot plugin manifests for startup auto-enable before plugin bootstrap plans plugin loading. Thanks @shakkernerd. - Agents/subagents: enforce `subagents.allowAgents` for explicit same-agent `sessions_spawn(agentId=...)` calls instead of auto-allowing requester self-targets. Fixes #72827. Thanks @oiGaDio. - ACP/sessions_spawn: let explicit `sessions_spawn(runtime="acp")` bootstrap turns run while `acp.dispatch.enabled=false` still blocks automatic ACP thread dispatch. Fixes #63591. Thanks @moeedahmed. - CLI/update: install npm global updates into a verified temporary prefix before swapping the package tree into place, preventing mixed old/new installs and stale packaged files from breaking `openclaw update` verification. Thanks @shakkernerd. diff --git a/src/gateway/server-startup-config.recovery.test.ts b/src/gateway/server-startup-config.recovery.test.ts index 8fccf042266..e6d2545a994 100644 --- a/src/gateway/server-startup-config.recovery.test.ts +++ b/src/gateway/server-startup-config.recovery.test.ts @@ -1,7 +1,56 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { ConfigFileSnapshot, OpenClawConfig } from "../config/types.js"; +import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { buildTestConfigSnapshot } from "./test-helpers.config-snapshots.js"; +const applyPluginAutoEnable = vi.hoisted(() => + vi.fn((params: { config: OpenClawConfig }) => ({ + config: params.config, + changes: [] as string[], + autoEnabledReasons: {} as Record, + })), +); +const pluginManifestRegistry = vi.hoisted(() => ({ plugins: [], diagnostics: [] })); +const pluginMetadataSnapshot = vi.hoisted( + (): PluginMetadataSnapshot => ({ + index: { + version: 1, + hostContractVersion: "test", + compatRegistryVersion: "test", + migrationVersion: 1, + policyHash: "policy", + generatedAtMs: 0, + installRecords: {}, + plugins: [], + diagnostics: [], + }, + registryDiagnostics: [], + manifestRegistry: pluginManifestRegistry, + plugins: [], + diagnostics: [], + byPluginId: new Map(), + normalizePluginId: (pluginId) => pluginId, + owners: { + channels: new Map(), + channelConfigs: new Map(), + providers: new Map(), + modelCatalogProviders: new Map(), + cliBackends: new Map(), + setupProviders: new Map(), + commandAliases: new Map(), + contracts: new Map(), + }, + metrics: { + registrySnapshotMs: 0, + manifestRegistryMs: 0, + ownerMapsMs: 0, + totalMs: 0, + indexPluginCount: 0, + manifestPluginCount: 0, + }, + }), +); + vi.mock("../config/config.js", () => ({ applyConfigOverrides: vi.fn((config: OpenClawConfig) => config), isNixMode: false, @@ -33,6 +82,10 @@ vi.mock("../config/config.js", () => ({ writeConfigFile: vi.fn(), })); +vi.mock("../config/plugin-auto-enable.js", () => ({ + applyPluginAutoEnable: (params: { config: OpenClawConfig }) => applyPluginAutoEnable(params), +})); + vi.mock("./config-recovery-notice.js", () => ({ enqueueConfigRecoveryNotice: vi.fn(), })); @@ -118,6 +171,7 @@ describe("gateway startup config recovery", () => { resolved: sourceConfig, runtimeConfig, config: runtimeConfig, + pluginMetadataSnapshot, } satisfies ConfigFileSnapshot; vi.mocked(configIo.readConfigFileSnapshot).mockResolvedValueOnce(snapshot); const log = { info: vi.fn(), warn: vi.fn() }; @@ -133,6 +187,11 @@ describe("gateway startup config recovery", () => { }); expect(configIo.readConfigFileSnapshot).toHaveBeenCalledTimes(1); + expect(applyPluginAutoEnable).toHaveBeenCalledWith({ + config: sourceConfig, + env: process.env, + manifestRegistry: pluginManifestRegistry, + }); expect(configIo.replaceConfigFile).not.toHaveBeenCalled(); expect(log.info).not.toHaveBeenCalled(); }); diff --git a/src/gateway/server-startup-config.ts b/src/gateway/server-startup-config.ts index 7a57ce75a7a..9fd648755e4 100644 --- a/src/gateway/server-startup-config.ts +++ b/src/gateway/server-startup-config.ts @@ -271,7 +271,13 @@ export async function loadGatewayStartupConfigSnapshot(params: { params.minimalTestGateway || degradedStartupConfig || degradedPluginConfig ? { config: configSnapshot.config, changes: [] as string[] } : await measure("config.snapshot.auto-enable", () => - applyPluginAutoEnable({ config: configSnapshot.sourceConfig, env: process.env }), + applyPluginAutoEnable({ + config: configSnapshot.sourceConfig, + env: process.env, + ...(configSnapshot.pluginMetadataSnapshot?.manifestRegistry + ? { manifestRegistry: configSnapshot.pluginMetadataSnapshot.manifestRegistry } + : {}), + }), ); if (autoEnable.changes.length === 0) { return {