mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:31:06 +00:00
refactor: reuse startup plugin metadata snapshot
This commit is contained in:
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
- Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.
|
- Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.
|
||||||
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
|
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
|
||||||
|
- Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd.
|
||||||
|
|
||||||
## 2026.4.26
|
## 2026.4.26
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import JSON5 from "json5";
|
import JSON5 from "json5";
|
||||||
|
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope-config.js";
|
||||||
import { ensureOwnerDisplaySecret } from "../agents/owner-display.js";
|
import { ensureOwnerDisplaySecret } from "../agents/owner-display.js";
|
||||||
import { applyRuntimeLegacyConfigMigrations } from "../commands/doctor/shared/runtime-compat-api.js";
|
import { applyRuntimeLegacyConfigMigrations } from "../commands/doctor/shared/runtime-compat-api.js";
|
||||||
import { loadDotEnv } from "../infra/dotenv.js";
|
import { loadDotEnv } from "../infra/dotenv.js";
|
||||||
@@ -23,6 +24,10 @@ import {
|
|||||||
resolveInstalledPluginIndexRecordsStorePath,
|
resolveInstalledPluginIndexRecordsStorePath,
|
||||||
writePersistedInstalledPluginIndexInstallRecordsSync,
|
writePersistedInstalledPluginIndexInstallRecordsSync,
|
||||||
} from "../plugins/installed-plugin-index-records.js";
|
} from "../plugins/installed-plugin-index-records.js";
|
||||||
|
import {
|
||||||
|
loadPluginMetadataSnapshot,
|
||||||
|
type PluginMetadataSnapshot,
|
||||||
|
} from "../plugins/plugin-metadata-snapshot.js";
|
||||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||||
import { isRecord } from "../utils.js";
|
import { isRecord } from "../utils.js";
|
||||||
import { VERSION } from "../version.js";
|
import { VERSION } from "../version.js";
|
||||||
@@ -1166,6 +1171,7 @@ function createConfigFileSnapshot(params: {
|
|||||||
issues: ConfigFileSnapshot["issues"];
|
issues: ConfigFileSnapshot["issues"];
|
||||||
warnings: ConfigFileSnapshot["warnings"];
|
warnings: ConfigFileSnapshot["warnings"];
|
||||||
legacyIssues: LegacyConfigIssue[];
|
legacyIssues: LegacyConfigIssue[];
|
||||||
|
pluginMetadataSnapshot?: PluginMetadataSnapshot;
|
||||||
}): ConfigFileSnapshot {
|
}): ConfigFileSnapshot {
|
||||||
const sourceConfig = asResolvedSourceConfig(params.sourceConfig);
|
const sourceConfig = asResolvedSourceConfig(params.sourceConfig);
|
||||||
const runtimeConfig = asRuntimeConfig(params.runtimeConfig);
|
const runtimeConfig = asRuntimeConfig(params.runtimeConfig);
|
||||||
@@ -1183,6 +1189,9 @@ function createConfigFileSnapshot(params: {
|
|||||||
issues: params.issues,
|
issues: params.issues,
|
||||||
warnings: params.warnings,
|
warnings: params.warnings,
|
||||||
legacyIssues: params.legacyIssues,
|
legacyIssues: params.legacyIssues,
|
||||||
|
...(params.pluginMetadataSnapshot
|
||||||
|
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1745,10 +1754,24 @@ export function createConfigIO(
|
|||||||
? hashConfigRaw(installMigration.persistedRootRaw)
|
? hashConfigRaw(installMigration.persistedRootRaw)
|
||||||
: hash;
|
: hash;
|
||||||
fallbackSourceConfig = coerceConfig(effectiveConfigRaw);
|
fallbackSourceConfig = coerceConfig(effectiveConfigRaw);
|
||||||
|
let pluginMetadataSnapshot: PluginMetadataSnapshot | undefined;
|
||||||
|
const loadValidationPluginMetadataSnapshot = (config: OpenClawConfig) => {
|
||||||
|
if (pluginMetadataSnapshot) {
|
||||||
|
return pluginMetadataSnapshot;
|
||||||
|
}
|
||||||
|
const defaultAgentId = resolveDefaultAgentId(config);
|
||||||
|
pluginMetadataSnapshot = loadPluginMetadataSnapshot({
|
||||||
|
config,
|
||||||
|
workspaceDir: resolveAgentWorkspaceDir(config, defaultAgentId),
|
||||||
|
env: deps.env,
|
||||||
|
});
|
||||||
|
return pluginMetadataSnapshot;
|
||||||
|
};
|
||||||
const validated = await deps.measure("config.snapshot.read.validate", () =>
|
const validated = await deps.measure("config.snapshot.read.validate", () =>
|
||||||
validateConfigObjectWithPlugins(effectiveConfigRaw, {
|
validateConfigObjectWithPlugins(effectiveConfigRaw, {
|
||||||
env: deps.env,
|
env: deps.env,
|
||||||
pluginValidation: overrides.pluginValidation,
|
pluginValidation: overrides.pluginValidation,
|
||||||
|
loadPluginMetadataSnapshot: loadValidationPluginMetadataSnapshot,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (!validated.ok) {
|
if (!validated.ok) {
|
||||||
@@ -1789,6 +1812,7 @@ export function createConfigIO(
|
|||||||
issues: [],
|
issues: [],
|
||||||
warnings: [...validated.warnings, ...envVarWarnings],
|
warnings: [...validated.warnings, ...envVarWarnings],
|
||||||
legacyIssues: legacyResolution.sourceLegacyIssues,
|
legacyIssues: legacyResolution.sourceLegacyIssues,
|
||||||
|
pluginMetadataSnapshot,
|
||||||
}),
|
}),
|
||||||
envSnapshotForRestore: readResolution.envSnapshotForRestore,
|
envSnapshotForRestore: readResolution.envSnapshotForRestore,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||||
import type {
|
import type {
|
||||||
SilentReplyPolicyShape,
|
SilentReplyPolicyShape,
|
||||||
SilentReplyRewriteShape,
|
SilentReplyRewriteShape,
|
||||||
@@ -184,4 +185,5 @@ export type ConfigFileSnapshot = {
|
|||||||
issues: ConfigValidationIssue[];
|
issues: ConfigValidationIssue[];
|
||||||
warnings: ConfigValidationIssue[];
|
warnings: ConfigValidationIssue[];
|
||||||
legacyIssues: LegacyConfigIssue[];
|
legacyIssues: LegacyConfigIssue[];
|
||||||
|
pluginMetadataSnapshot?: PluginMetadataSnapshot;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ describe("validateConfigObjectWithPlugins bundled allowlist compatibility", () =
|
|||||||
const result = validateConfigObjectWithPlugins(
|
const result = validateConfigObjectWithPlugins(
|
||||||
{
|
{
|
||||||
plugins: {
|
plugins: {
|
||||||
|
allow: ["opik"],
|
||||||
entries: {
|
entries: {
|
||||||
opik: {
|
opik: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -261,4 +262,30 @@ describe("validateConfigObjectWithPlugins bundled allowlist compatibility", () =
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads a plugin metadata snapshot once during plugin validation", () => {
|
||||||
|
const loadPluginMetadataSnapshot = vi.fn((_config: unknown) => ({
|
||||||
|
manifestRegistry: createPluginConfigSchemaRegistry(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = validateConfigObjectWithPlugins(
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
allow: ["opik"],
|
||||||
|
entries: {
|
||||||
|
opik: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loadPluginMetadataSnapshot,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
expect(loadPluginMetadataSnapshot).toHaveBeenCalledOnce();
|
||||||
|
expect(mockLoadPluginManifestRegistry).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -711,6 +711,9 @@ type ValidateConfigWithPluginsParams = {
|
|||||||
env?: NodeJS.ProcessEnv;
|
env?: NodeJS.ProcessEnv;
|
||||||
pluginValidation?: "full" | "skip";
|
pluginValidation?: "full" | "skip";
|
||||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "manifestRegistry">;
|
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "manifestRegistry">;
|
||||||
|
loadPluginMetadataSnapshot?: (
|
||||||
|
config: OpenClawConfig,
|
||||||
|
) => Pick<PluginMetadataSnapshot, "manifestRegistry">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function validateConfigObjectWithPlugins(
|
export function validateConfigObjectWithPlugins(
|
||||||
@@ -722,6 +725,7 @@ export function validateConfigObjectWithPlugins(
|
|||||||
env: params?.env,
|
env: params?.env,
|
||||||
pluginValidation: params?.pluginValidation ?? "full",
|
pluginValidation: params?.pluginValidation ?? "full",
|
||||||
pluginMetadataSnapshot: params?.pluginMetadataSnapshot,
|
pluginMetadataSnapshot: params?.pluginMetadataSnapshot,
|
||||||
|
loadPluginMetadataSnapshot: params?.loadPluginMetadataSnapshot,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,6 +738,7 @@ export function validateConfigObjectRawWithPlugins(
|
|||||||
env: params?.env,
|
env: params?.env,
|
||||||
pluginValidation: params?.pluginValidation ?? "full",
|
pluginValidation: params?.pluginValidation ?? "full",
|
||||||
pluginMetadataSnapshot: params?.pluginMetadataSnapshot,
|
pluginMetadataSnapshot: params?.pluginMetadataSnapshot,
|
||||||
|
loadPluginMetadataSnapshot: params?.loadPluginMetadataSnapshot,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,6 +815,11 @@ function validateConfigObjectWithPluginsBase(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadValidationRegistry = (): RegistryInfo => {
|
const loadValidationRegistry = (): RegistryInfo => {
|
||||||
|
const pluginMetadataSnapshot = opts.loadPluginMetadataSnapshot?.(config);
|
||||||
|
if (pluginMetadataSnapshot) {
|
||||||
|
registryInfo = { registry: pluginMetadataSnapshot.manifestRegistry };
|
||||||
|
return registryInfo;
|
||||||
|
}
|
||||||
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
||||||
const registry = loadPluginManifestRegistryForPluginRegistry({
|
const registry = loadPluginManifestRegistryForPluginRegistry({
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
|
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||||
|
|
||||||
const applyPluginAutoEnable = vi.hoisted(() =>
|
const applyPluginAutoEnable = vi.hoisted(() =>
|
||||||
vi.fn((params: { config: unknown }) => ({
|
vi.fn((params: { config: unknown }) => ({
|
||||||
@@ -22,6 +23,45 @@ const resolveBundledRuntimeDependencyPackageInstallRoot = vi.hoisted(() =>
|
|||||||
vi.fn((_packageRoot: string, _params: unknown) => "/runtime"),
|
vi.fn((_packageRoot: string, _params: unknown) => "/runtime"),
|
||||||
);
|
);
|
||||||
const pluginManifestRegistry = vi.hoisted(() => ({ plugins: [], diagnostics: [] }));
|
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,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
const pluginLookUpTableMetrics = vi.hoisted(() => ({
|
const pluginLookUpTableMetrics = vi.hoisted(() => ({
|
||||||
registrySnapshotMs: 0,
|
registrySnapshotMs: 0,
|
||||||
manifestRegistryMs: 0,
|
manifestRegistryMs: 0,
|
||||||
@@ -259,6 +299,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
|
|||||||
cfgAtStart: runtimeConfig,
|
cfgAtStart: runtimeConfig,
|
||||||
activationSourceConfig: sourceConfig,
|
activationSourceConfig: sourceConfig,
|
||||||
startupRuntimeConfig: runtimeConfig,
|
startupRuntimeConfig: runtimeConfig,
|
||||||
|
pluginMetadataSnapshot,
|
||||||
minimalTestGateway: false,
|
minimalTestGateway: false,
|
||||||
log,
|
log,
|
||||||
});
|
});
|
||||||
@@ -270,6 +311,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
|
|||||||
expect(loadPluginLookUpTable).toHaveBeenCalledWith(
|
expect(loadPluginLookUpTable).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
activationSourceConfig: sourceConfig,
|
activationSourceConfig: sourceConfig,
|
||||||
|
metadataSnapshot: pluginMetadataSnapshot,
|
||||||
config: expect.objectContaining({
|
config: expect.objectContaining({
|
||||||
channels: expect.objectContaining({
|
channels: expect.objectContaining({
|
||||||
telegram: expect.objectContaining({
|
telegram: expect.objectContaining({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
scanBundledPluginRuntimeDeps,
|
scanBundledPluginRuntimeDeps,
|
||||||
} from "../plugins/bundled-runtime-deps.js";
|
} from "../plugins/bundled-runtime-deps.js";
|
||||||
import { loadPluginLookUpTable } from "../plugins/plugin-lookup-table.js";
|
import { loadPluginLookUpTable } from "../plugins/plugin-lookup-table.js";
|
||||||
|
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||||
import { createEmptyPluginRegistry } from "../plugins/registry.js";
|
import { createEmptyPluginRegistry } from "../plugins/registry.js";
|
||||||
import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js";
|
import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js";
|
||||||
import { mergeActivationSectionsIntoRuntimeConfig } from "./plugin-activation-runtime-config.js";
|
import { mergeActivationSectionsIntoRuntimeConfig } from "./plugin-activation-runtime-config.js";
|
||||||
@@ -95,6 +96,7 @@ export async function prepareGatewayPluginBootstrap(params: {
|
|||||||
cfgAtStart: OpenClawConfig;
|
cfgAtStart: OpenClawConfig;
|
||||||
activationSourceConfig?: OpenClawConfig;
|
activationSourceConfig?: OpenClawConfig;
|
||||||
startupRuntimeConfig: OpenClawConfig;
|
startupRuntimeConfig: OpenClawConfig;
|
||||||
|
pluginMetadataSnapshot?: PluginMetadataSnapshot;
|
||||||
minimalTestGateway: boolean;
|
minimalTestGateway: boolean;
|
||||||
log: GatewayPluginBootstrapLog;
|
log: GatewayPluginBootstrapLog;
|
||||||
}) {
|
}) {
|
||||||
@@ -149,6 +151,7 @@ export async function prepareGatewayPluginBootstrap(params: {
|
|||||||
workspaceDir: defaultWorkspaceDir,
|
workspaceDir: defaultWorkspaceDir,
|
||||||
env: process.env,
|
env: process.env,
|
||||||
activationSourceConfig,
|
activationSourceConfig,
|
||||||
|
metadataSnapshot: params.pluginMetadataSnapshot,
|
||||||
});
|
});
|
||||||
const deferredConfiguredChannelPluginIds = [
|
const deferredConfiguredChannelPluginIds = [
|
||||||
...(pluginLookUpTable?.startup.configuredDeferredChannelPluginIds ?? []),
|
...(pluginLookUpTable?.startup.configuredDeferredChannelPluginIds ?? []),
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ export async function startGatewayServer(
|
|||||||
cfgAtStart,
|
cfgAtStart,
|
||||||
activationSourceConfig: startupActivationSourceConfig,
|
activationSourceConfig: startupActivationSourceConfig,
|
||||||
startupRuntimeConfig,
|
startupRuntimeConfig,
|
||||||
|
pluginMetadataSnapshot: configSnapshot.pluginMetadataSnapshot,
|
||||||
minimalTestGateway,
|
minimalTestGateway,
|
||||||
log,
|
log,
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user