mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 13:20:42 +00:00
fix: reuse turn plugin metadata snapshot
This commit is contained in:
@@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Voice Call/realtime: bound the paced Twilio audio queue and close overloaded realtime streams before provider audio can pile up behind the websocket backpressure guard. Thanks @vincentkoc.
|
||||
- Docs: clarify that IRC uses raw TCP/TLS sockets outside operator-managed forward proxy routing, so direct IRC egress should be explicitly approved before enabling IRC. Thanks @jesse-merhi.
|
||||
- Gateway/performance: defer non-readiness sidecars until after the ready signal, avoid hot-path channel plugin barrel imports, and fast-path trusted bundled plugin metadata during Gateway startup.
|
||||
- Gateway/performance: reuse the compatible plugin metadata snapshot across dashboard and channel agent turns so auto-enabled runtime config does not repeatedly rescan plugin metadata before provider calls. Thanks @shakkernerd.
|
||||
- Gateway/performance: avoid importing `jiti` on native-loadable plugin startup paths, so compiled bundled plugin surfaces do not pay source-transform loader cost unless fallback loading is actually needed.
|
||||
- Gateway/diagnostics: add startup phase spans, active work labels, stale terminal bridge markers, and default sync-I/O tracing in `pnpm gateway:watch` so slow Gateway turns are easier to attribute from logs and stability diagnostics.
|
||||
- Plugins/loader: preserve real compiled plugin module evaluation errors on the native fast path instead of treating every thrown `.js` module as a source-transform fallback miss. Thanks @vincentkoc.
|
||||
|
||||
@@ -655,7 +655,12 @@ export async function startGatewayServer(
|
||||
baseMethods,
|
||||
runtimePluginsLoaded,
|
||||
} = pluginBootstrap;
|
||||
setCurrentPluginMetadataSnapshot(pluginLookUpTable, { config: gatewayPluginConfigAtStart });
|
||||
setCurrentPluginMetadataSnapshot(pluginLookUpTable, {
|
||||
config: startupActivationSourceConfig,
|
||||
compatibleConfigs: [gatewayPluginConfigAtStart],
|
||||
env: process.env,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
});
|
||||
if (pluginLookUpTable) {
|
||||
const metrics = pluginLookUpTable.metrics;
|
||||
startupTrace.detail("plugins.lookup-table", [
|
||||
@@ -1178,7 +1183,11 @@ export async function startGatewayServer(
|
||||
}
|
||||
}
|
||||
await params.beforeReplace(channelsToStopBeforeReplace);
|
||||
setCurrentPluginMetadataSnapshot(nextPluginLookUpTable, { config: params.nextConfig });
|
||||
setCurrentPluginMetadataSnapshot(nextPluginLookUpTable, {
|
||||
config: params.nextConfig,
|
||||
env: process.env,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
});
|
||||
const loaded = prepareGatewayPluginLoad({
|
||||
cfg: params.nextConfig,
|
||||
workspaceDir: defaultWorkspaceDir,
|
||||
|
||||
@@ -160,6 +160,27 @@ describe("current plugin metadata snapshot", () => {
|
||||
expect(getCurrentPluginMetadataSnapshot({ config: autoEnabledConfig })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("accepts explicit compatible configs for gateway runtime reuse", () => {
|
||||
const sourceConfig = { channels: { telegram: { botToken: "token" } } };
|
||||
const runtimeConfig = {
|
||||
...sourceConfig,
|
||||
plugins: { allow: ["telegram"] },
|
||||
};
|
||||
const snapshot = createSnapshot({ config: sourceConfig, workspaceDir: "/workspace" });
|
||||
setCurrentPluginMetadataSnapshot(snapshot, {
|
||||
config: sourceConfig,
|
||||
compatibleConfigs: [runtimeConfig],
|
||||
workspaceDir: "/workspace",
|
||||
});
|
||||
|
||||
expect(
|
||||
getCurrentPluginMetadataSnapshot({ config: sourceConfig, workspaceDir: "/workspace" }),
|
||||
).toBe(snapshot);
|
||||
expect(
|
||||
getCurrentPluginMetadataSnapshot({ config: runtimeConfig, workspaceDir: "/workspace" }),
|
||||
).toBe(snapshot);
|
||||
});
|
||||
|
||||
it("clears the current snapshot", () => {
|
||||
setCurrentPluginMetadataSnapshot(createSnapshot());
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
|
||||
@@ -25,8 +25,26 @@ export function resolvePluginMetadataControlPlaneFingerprint(
|
||||
// never accumulate historical metadata snapshots here.
|
||||
export function setCurrentPluginMetadataSnapshot(
|
||||
snapshot: PluginMetadataSnapshot | undefined,
|
||||
options: { config?: OpenClawConfig; env?: NodeJS.ProcessEnv; workspaceDir?: string } = {},
|
||||
options: {
|
||||
config?: OpenClawConfig;
|
||||
compatibleConfigs?: readonly OpenClawConfig[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): void {
|
||||
const compatiblePolicyHashes = snapshot
|
||||
? options.compatibleConfigs?.map((config) => resolveInstalledPluginIndexPolicyHash(config))
|
||||
: undefined;
|
||||
const compatibleConfigFingerprints = snapshot
|
||||
? options.compatibleConfigs?.map((config, index) =>
|
||||
resolvePluginMetadataControlPlaneFingerprint(config, {
|
||||
env: options.env,
|
||||
index: snapshot.index,
|
||||
policyHash: compatiblePolicyHashes?.[index],
|
||||
workspaceDir: options.workspaceDir ?? snapshot.workspaceDir,
|
||||
}),
|
||||
)
|
||||
: undefined;
|
||||
setCurrentPluginMetadataSnapshotState(
|
||||
snapshot,
|
||||
snapshot
|
||||
@@ -37,6 +55,8 @@ export function setCurrentPluginMetadataSnapshot(
|
||||
workspaceDir: options.workspaceDir ?? snapshot.workspaceDir,
|
||||
})
|
||||
: undefined,
|
||||
compatiblePolicyHashes,
|
||||
compatibleConfigFingerprints,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,16 +72,24 @@ export function getCurrentPluginMetadataSnapshot(
|
||||
allowWorkspaceScopedSnapshot?: boolean;
|
||||
} = {},
|
||||
): PluginMetadataSnapshot | undefined {
|
||||
const { snapshot: rawSnapshot, configFingerprint } = getCurrentPluginMetadataSnapshotState();
|
||||
const {
|
||||
snapshot: rawSnapshot,
|
||||
configFingerprint,
|
||||
compatiblePolicyHashes,
|
||||
compatibleConfigFingerprints,
|
||||
} = getCurrentPluginMetadataSnapshotState();
|
||||
const snapshot = rawSnapshot as PluginMetadataSnapshot | undefined;
|
||||
if (!snapshot) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
params.config &&
|
||||
snapshot.policyHash !== resolveInstalledPluginIndexPolicyHash(params.config)
|
||||
) {
|
||||
return undefined;
|
||||
const requestedPolicyHash = params.config
|
||||
? resolveInstalledPluginIndexPolicyHash(params.config)
|
||||
: undefined;
|
||||
if (requestedPolicyHash && snapshot.policyHash !== requestedPolicyHash) {
|
||||
const compatiblePolicies = new Set(compatiblePolicyHashes ?? []);
|
||||
if (!compatiblePolicies.has(requestedPolicyHash)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const requestedWorkspaceDir =
|
||||
params.workspaceDir ??
|
||||
@@ -70,13 +98,15 @@ export function getCurrentPluginMetadataSnapshot(
|
||||
const requestedConfigFingerprint = resolvePluginMetadataControlPlaneFingerprint(params.config, {
|
||||
env: params.env,
|
||||
index: snapshot.index,
|
||||
policyHash: snapshot.policyHash,
|
||||
policyHash: requestedPolicyHash,
|
||||
workspaceDir: requestedWorkspaceDir,
|
||||
});
|
||||
if (configFingerprint && configFingerprint !== requestedConfigFingerprint) {
|
||||
return undefined;
|
||||
}
|
||||
if (snapshot.configFingerprint && snapshot.configFingerprint !== requestedConfigFingerprint) {
|
||||
const compatibleFingerprints = new Set(compatibleConfigFingerprints ?? []);
|
||||
const fingerprintMatches =
|
||||
configFingerprint === requestedConfigFingerprint ||
|
||||
snapshot.configFingerprint === requestedConfigFingerprint ||
|
||||
compatibleFingerprints.has(requestedConfigFingerprint);
|
||||
if (!fingerprintMatches) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,41 @@
|
||||
let currentPluginMetadataSnapshot: unknown;
|
||||
let currentPluginMetadataSnapshotConfigFingerprint: string | undefined;
|
||||
let currentPluginMetadataSnapshotCompatiblePolicyHashes: readonly string[] | undefined;
|
||||
let currentPluginMetadataSnapshotCompatibleConfigFingerprints: readonly string[] | undefined;
|
||||
|
||||
export function setCurrentPluginMetadataSnapshotState(
|
||||
snapshot: unknown,
|
||||
configFingerprint: string | undefined,
|
||||
compatiblePolicyHashes?: readonly string[],
|
||||
compatibleConfigFingerprints?: readonly string[],
|
||||
): void {
|
||||
currentPluginMetadataSnapshot = snapshot;
|
||||
currentPluginMetadataSnapshotConfigFingerprint = snapshot ? configFingerprint : undefined;
|
||||
currentPluginMetadataSnapshotCompatiblePolicyHashes = snapshot
|
||||
? compatiblePolicyHashes
|
||||
: undefined;
|
||||
currentPluginMetadataSnapshotCompatibleConfigFingerprints = snapshot
|
||||
? compatibleConfigFingerprints
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function clearCurrentPluginMetadataSnapshotState(): void {
|
||||
currentPluginMetadataSnapshot = undefined;
|
||||
currentPluginMetadataSnapshotConfigFingerprint = undefined;
|
||||
currentPluginMetadataSnapshotCompatiblePolicyHashes = undefined;
|
||||
currentPluginMetadataSnapshotCompatibleConfigFingerprints = undefined;
|
||||
}
|
||||
|
||||
export function getCurrentPluginMetadataSnapshotState(): {
|
||||
snapshot: unknown;
|
||||
configFingerprint: string | undefined;
|
||||
compatiblePolicyHashes: readonly string[] | undefined;
|
||||
compatibleConfigFingerprints: readonly string[] | undefined;
|
||||
} {
|
||||
return {
|
||||
snapshot: currentPluginMetadataSnapshot,
|
||||
configFingerprint: currentPluginMetadataSnapshotConfigFingerprint,
|
||||
compatiblePolicyHashes: currentPluginMetadataSnapshotCompatiblePolicyHashes,
|
||||
compatibleConfigFingerprints: currentPluginMetadataSnapshotCompatibleConfigFingerprints,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ const pluginRegistryMocks = vi.hoisted(() => {
|
||||
diagnostics: [],
|
||||
}));
|
||||
return {
|
||||
getCurrentPluginMetadataSnapshot: vi.fn(),
|
||||
loadPluginManifestRegistryForInstalledIndex: loadManifestRegistry,
|
||||
loadPluginManifestRegistryForPluginRegistry: loadManifestRegistry,
|
||||
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
|
||||
@@ -61,6 +62,10 @@ const pluginRegistryMocks = vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../plugins/current-plugin-metadata-snapshot.js", () => ({
|
||||
getCurrentPluginMetadataSnapshot: pluginRegistryMocks.getCurrentPluginMetadataSnapshot,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/manifest-registry-installed.js", () => ({
|
||||
loadPluginManifestRegistryForInstalledIndex:
|
||||
pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex,
|
||||
@@ -85,6 +90,8 @@ describe("provider env vars dynamic manifest metadata", () => {
|
||||
});
|
||||
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReset();
|
||||
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
|
||||
pluginRegistryMocks.getCurrentPluginMetadataSnapshot.mockReset();
|
||||
pluginRegistryMocks.getCurrentPluginMetadataSnapshot.mockReturnValue(undefined);
|
||||
pluginRegistryMocks.loadPluginMetadataSnapshot.mockClear();
|
||||
__testing.resetProviderEnvVarCachesForTests();
|
||||
});
|
||||
@@ -177,6 +184,55 @@ describe("provider env vars dynamic manifest metadata", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses the current compatible metadata snapshot for workspace auth evidence", async () => {
|
||||
pluginRegistryMocks.getCurrentPluginMetadataSnapshot.mockReturnValue({
|
||||
index: {
|
||||
plugins: [
|
||||
{
|
||||
pluginId: "external-cloud",
|
||||
origin: "global",
|
||||
enabled: true,
|
||||
enabledByDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: "external-cloud",
|
||||
origin: "global",
|
||||
setup: {
|
||||
providers: [
|
||||
{
|
||||
id: "external-cloud",
|
||||
authEvidence: [
|
||||
{
|
||||
type: "local-file-with-env",
|
||||
fileEnvVar: "EXTERNAL_CLOUD_CREDENTIALS",
|
||||
credentialMarker: "external-cloud-local-credentials",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveProviderAuthEvidence({
|
||||
config: {},
|
||||
workspaceDir: "/workspace",
|
||||
})["external-cloud"],
|
||||
).toEqual([
|
||||
{
|
||||
type: "local-file-with-env",
|
||||
fileEnvVar: "EXTERNAL_CLOUD_CREDENTIALS",
|
||||
credentialMarker: "external-cloud-local-credentials",
|
||||
},
|
||||
]);
|
||||
expect(pluginRegistryMocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("excludes untrusted workspace plugin auth evidence by default", async () => {
|
||||
pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry.mockReturnValue({
|
||||
plugins: [
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { resolveProviderAuthAliasMap } from "../agents/provider-auth-aliases.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { isInstalledPluginEnabled } from "../plugins/installed-plugin-index.js";
|
||||
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
|
||||
import {
|
||||
isWorkspacePluginAllowedByConfig,
|
||||
normalizePluginConfigId,
|
||||
} from "../plugins/plugin-config-trust.js";
|
||||
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import {
|
||||
loadPluginMetadataSnapshot,
|
||||
type PluginMetadataSnapshot,
|
||||
} from "../plugins/plugin-metadata-snapshot.js";
|
||||
import { hasKind } from "../plugins/slots.js";
|
||||
|
||||
const CORE_PROVIDER_AUTH_ENV_VAR_CANDIDATES = {
|
||||
@@ -115,15 +119,31 @@ function appendUniqueAuthEvidence(
|
||||
}
|
||||
}
|
||||
|
||||
function resolveProviderMetadataSnapshot(
|
||||
params?: ProviderEnvVarLookupParams,
|
||||
): PluginMetadataSnapshot {
|
||||
const config = params?.config ?? {};
|
||||
const env = params?.env ?? process.env;
|
||||
const current = getCurrentPluginMetadataSnapshot({
|
||||
config,
|
||||
env,
|
||||
...(params?.workspaceDir !== undefined ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
if (current) {
|
||||
return current;
|
||||
}
|
||||
return loadPluginMetadataSnapshot({
|
||||
config,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env,
|
||||
preferPersisted: false,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveManifestProviderAuthEnvVarCandidates(
|
||||
params?: ProviderEnvVarLookupParams,
|
||||
): Record<string, string[]> {
|
||||
const snapshot = loadPluginMetadataSnapshot({
|
||||
config: params?.config ?? {},
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env: params?.env ?? process.env,
|
||||
preferPersisted: false,
|
||||
});
|
||||
const snapshot = resolveProviderMetadataSnapshot(params);
|
||||
const candidates: Record<string, string[]> = {};
|
||||
for (const plugin of snapshot.plugins) {
|
||||
if (!shouldUsePluginProviderEnvVars(plugin, params)) {
|
||||
@@ -155,12 +175,7 @@ function resolveManifestProviderAuthEnvVarCandidates(
|
||||
function resolveManifestProviderAuthEvidence(
|
||||
params?: ProviderEnvVarLookupParams,
|
||||
): Record<string, ProviderAuthEvidence[]> {
|
||||
const snapshot = loadPluginMetadataSnapshot({
|
||||
config: params?.config ?? {},
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env: params?.env ?? process.env,
|
||||
preferPersisted: false,
|
||||
});
|
||||
const snapshot = resolveProviderMetadataSnapshot(params);
|
||||
const evidenceByProvider: Record<string, ProviderAuthEvidence[]> = {};
|
||||
for (const plugin of snapshot.plugins) {
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user