fix: reuse plugin snapshot for embedded settings

This commit is contained in:
Shakker
2026-05-06 07:00:23 +01:00
parent 852b9e7246
commit ba1800e1bd
3 changed files with 127 additions and 11 deletions

View File

@@ -9,7 +9,11 @@ import {
normalizePluginsConfigWithResolver,
resolveEffectivePluginActivationState,
} from "../plugins/config-policy.js";
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import {
isPluginMetadataSnapshotCompatible,
loadPluginMetadataSnapshot,
type PluginMetadataSnapshot,
} from "../plugins/plugin-metadata-snapshot.js";
import { loadEmbeddedPiMcpConfig } from "./embedded-pi-mcp.js";
const log = createSubsystemLogger("embedded-pi-settings");
@@ -61,23 +65,37 @@ function loadBundleSettingsFile(params: {
export function loadEnabledBundlePiSettingsSnapshot(params: {
cwd: string;
cfg?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
pluginMetadataSnapshot?: PluginMetadataSnapshot;
}): PiSettingsSnapshot {
const workspaceDir = params.cwd.trim();
if (!workspaceDir) {
return {};
}
const metadataSnapshot = loadPluginMetadataSnapshot({
workspaceDir,
config: params.cfg ?? {},
env: process.env,
});
const config = params.cfg ?? {};
const env = params.env ?? process.env;
const providedSnapshot = params.pluginMetadataSnapshot;
const metadataSnapshot =
providedSnapshot &&
isPluginMetadataSnapshotCompatible({
snapshot: providedSnapshot,
config,
env,
workspaceDir,
})
? providedSnapshot
: loadPluginMetadataSnapshot({
workspaceDir,
config,
env,
});
const registry = metadataSnapshot.manifestRegistry;
if (registry.plugins.length === 0) {
return {};
}
const normalizedPlugins = normalizePluginsConfigWithResolver(
params.cfg?.plugins,
config.plugins,
metadataSnapshot.normalizePluginId,
);
let snapshot: PiSettingsSnapshot = {};
@@ -91,7 +109,7 @@ export function loadEnabledBundlePiSettingsSnapshot(params: {
id: record.id,
origin: record.origin,
config: normalizedPlugins,
rootConfig: params.cfg,
rootConfig: config,
});
if (!activationState.activated) {
continue;
@@ -110,7 +128,7 @@ export function loadEnabledBundlePiSettingsSnapshot(params: {
const embeddedPiMcp = loadEmbeddedPiMcpConfig({
workspaceDir,
cfg: params.cfg,
cfg: config,
});
for (const diagnostic of embeddedPiMcp.diagnostics) {
log.warn(`bundle MCP skipped for ${diagnostic.pluginId}: ${diagnostic.message}`);

View File

@@ -3,6 +3,11 @@ import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
const pluginMetadataSnapshotMocks = vi.hoisted(() => ({
isPluginMetadataSnapshotCompatible: vi.fn(),
loadPluginMetadataSnapshot: vi.fn(),
}));
vi.mock("../infra/boundary-file-read.js", async () => {
const fs = await import("node:fs");
return {
@@ -107,11 +112,17 @@ vi.mock("../plugins/plugin-metadata-snapshot.js", async () => {
],
};
};
return {
loadPluginMetadataSnapshot: (params: { workspaceDir?: string }) => ({
pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible.mockImplementation(() => false);
pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot.mockImplementation(
(params: { workspaceDir?: string }) => ({
manifestRegistry: loadRegistry(params),
normalizePluginId: (id: string) => id.trim(),
}),
);
return {
isPluginMetadataSnapshotCompatible:
pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible,
loadPluginMetadataSnapshot: pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot,
};
});
@@ -161,6 +172,8 @@ const tempDirs = createTrackedTempDirs();
afterEach(async () => {
await tempDirs.cleanup();
pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible.mockClear();
pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot.mockClear();
});
async function createWorkspaceBundle(params: {
@@ -181,6 +194,87 @@ async function createWorkspaceBundle(params: {
}
describe("loadEnabledBundlePiSettingsSnapshot", () => {
it("reuses a compatible plugin metadata snapshot without loading a fresh one", async () => {
const workspaceDir = await tempDirs.make("openclaw-workspace-");
const pluginRoot = await createWorkspaceBundle({ workspaceDir });
const resolvedPluginRoot = await fs.realpath(pluginRoot);
await fs.writeFile(
path.join(pluginRoot, "settings.json"),
JSON.stringify({ hideThinkingBlock: true }),
"utf-8",
);
pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible.mockReturnValueOnce(true);
pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot.mockClear();
const snapshot = loadEnabledBundlePiSettingsSnapshot({
cwd: workspaceDir,
cfg: {
plugins: {
entries: {
"claude-bundle": { enabled: true },
},
},
},
pluginMetadataSnapshot: {
manifestRegistry: {
diagnostics: [],
plugins: [
{
id: "claude-bundle",
origin: "workspace",
format: "bundle",
bundleFormat: "claude",
settingsFiles: ["settings.json"],
rootDir: resolvedPluginRoot,
},
],
},
normalizePluginId: (id: string) => id.trim(),
} as unknown as Parameters<
typeof loadEnabledBundlePiSettingsSnapshot
>[0]["pluginMetadataSnapshot"],
});
expect(snapshot.hideThinkingBlock).toBe(true);
expect(pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible).toHaveBeenCalledOnce();
expect(pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
});
it("falls back to a fresh plugin metadata load for an incompatible snapshot", async () => {
const workspaceDir = await tempDirs.make("openclaw-workspace-");
const pluginRoot = await createWorkspaceBundle({ workspaceDir });
await fs.writeFile(
path.join(pluginRoot, "settings.json"),
JSON.stringify({ hideThinkingBlock: true }),
"utf-8",
);
pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible.mockReturnValueOnce(false);
pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot.mockClear();
const snapshot = loadEnabledBundlePiSettingsSnapshot({
cwd: workspaceDir,
cfg: {
plugins: {
entries: {
"claude-bundle": { enabled: true },
},
},
},
pluginMetadataSnapshot: {
manifestRegistry: { diagnostics: [], plugins: [] },
normalizePluginId: (id: string) => id.trim(),
} as unknown as Parameters<
typeof loadEnabledBundlePiSettingsSnapshot
>[0]["pluginMetadataSnapshot"],
});
expect(snapshot.hideThinkingBlock).toBe(true);
expect(pluginMetadataSnapshotMocks.isPluginMetadataSnapshotCompatible).toHaveBeenCalledOnce();
expect(pluginMetadataSnapshotMocks.loadPluginMetadataSnapshot).toHaveBeenCalledOnce();
});
it("loads sanitized settings and MCP defaults from enabled bundle plugins", async () => {
const workspaceDir = await tempDirs.make("openclaw-workspace-");
const pluginRoot = await createWorkspaceBundle({ workspaceDir });

View File

@@ -1,5 +1,6 @@
import { SettingsManager } from "@mariozechner/pi-coding-agent";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import {
buildEmbeddedPiSettingsSnapshot,
loadEnabledBundlePiSettingsSnapshot,
@@ -11,12 +12,14 @@ function createEmbeddedPiSettingsManager(params: {
cwd: string;
agentDir: string;
cfg?: OpenClawConfig;
pluginMetadataSnapshot?: PluginMetadataSnapshot;
}): SettingsManager {
const fileSettingsManager = SettingsManager.create(params.cwd, params.agentDir);
const policy = resolveEmbeddedPiProjectSettingsPolicy(params.cfg);
const pluginSettings = loadEnabledBundlePiSettingsSnapshot({
cwd: params.cwd,
cfg: params.cfg,
pluginMetadataSnapshot: params.pluginMetadataSnapshot,
});
const hasPluginSettings = Object.keys(pluginSettings).length > 0;
if (policy === "trusted" && !hasPluginSettings) {
@@ -46,6 +49,7 @@ export function createPreparedEmbeddedPiSettingsManager(params: {
cwd: string;
agentDir: string;
cfg?: OpenClawConfig;
pluginMetadataSnapshot?: PluginMetadataSnapshot;
/** Resolved context window budget so reserve-token floor can be capped for small models. */
contextTokenBudget?: number;
}): SettingsManager {