mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:40:44 +00:00
perf: snapshot pi project settings
This commit is contained in:
159
src/agents/pi-project-settings-snapshot.ts
Normal file
159
src/agents/pi-project-settings-snapshot.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import type { SettingsManager } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { applyMergePatch } from "../config/merge-patch.js";
|
||||||
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
|
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||||
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
|
import type { BundleMcpServerConfig } from "../plugins/bundle-mcp.js";
|
||||||
|
import {
|
||||||
|
normalizePluginsConfig,
|
||||||
|
resolveEffectivePluginActivationState,
|
||||||
|
} from "../plugins/config-state.js";
|
||||||
|
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||||
|
import { isRecord } from "../utils.js";
|
||||||
|
import { loadEmbeddedPiMcpConfig } from "./embedded-pi-mcp.js";
|
||||||
|
|
||||||
|
const log = createSubsystemLogger("embedded-pi-settings");
|
||||||
|
|
||||||
|
export const DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY = "sanitize";
|
||||||
|
export const SANITIZED_PROJECT_PI_KEYS = ["shellPath", "shellCommandPrefix"] as const;
|
||||||
|
|
||||||
|
export type EmbeddedPiProjectSettingsPolicy = "trusted" | "sanitize" | "ignore";
|
||||||
|
|
||||||
|
export type PiSettingsSnapshot = ReturnType<SettingsManager["getGlobalSettings"]> & {
|
||||||
|
mcpServers?: Record<string, BundleMcpServerConfig>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sanitizePiSettingsSnapshot(settings: PiSettingsSnapshot): PiSettingsSnapshot {
|
||||||
|
const sanitized = { ...settings };
|
||||||
|
// Never allow plugin or workspace-local settings to override shell execution behavior.
|
||||||
|
for (const key of SANITIZED_PROJECT_PI_KEYS) {
|
||||||
|
delete sanitized[key];
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeProjectSettings(settings: PiSettingsSnapshot): PiSettingsSnapshot {
|
||||||
|
return sanitizePiSettingsSnapshot(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBundleSettingsFile(params: {
|
||||||
|
rootDir: string;
|
||||||
|
relativePath: string;
|
||||||
|
}): PiSettingsSnapshot | null {
|
||||||
|
const absolutePath = path.join(params.rootDir, params.relativePath);
|
||||||
|
const opened = openBoundaryFileSync({
|
||||||
|
absolutePath,
|
||||||
|
rootPath: params.rootDir,
|
||||||
|
boundaryLabel: "plugin root",
|
||||||
|
rejectHardlinks: true,
|
||||||
|
});
|
||||||
|
if (!opened.ok) {
|
||||||
|
log.warn(`skipping unsafe bundle settings file: ${absolutePath}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = JSON.parse(fs.readFileSync(opened.fd, "utf-8")) as unknown;
|
||||||
|
if (!isRecord(raw)) {
|
||||||
|
log.warn(`skipping bundle settings file with non-object JSON: ${absolutePath}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sanitizePiSettingsSnapshot(raw as PiSettingsSnapshot);
|
||||||
|
} catch (error) {
|
||||||
|
log.warn(`failed to parse bundle settings file ${absolutePath}: ${String(error)}`);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
fs.closeSync(opened.fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadEnabledBundlePiSettingsSnapshot(params: {
|
||||||
|
cwd: string;
|
||||||
|
cfg?: OpenClawConfig;
|
||||||
|
}): PiSettingsSnapshot {
|
||||||
|
const workspaceDir = params.cwd.trim();
|
||||||
|
if (!workspaceDir) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const registry = loadPluginManifestRegistry({
|
||||||
|
workspaceDir,
|
||||||
|
config: params.cfg,
|
||||||
|
});
|
||||||
|
if (registry.plugins.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedPlugins = normalizePluginsConfig(params.cfg?.plugins);
|
||||||
|
let snapshot: PiSettingsSnapshot = {};
|
||||||
|
|
||||||
|
for (const record of registry.plugins) {
|
||||||
|
const settingsFiles = record.settingsFiles ?? [];
|
||||||
|
if (record.format !== "bundle" || settingsFiles.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const activationState = resolveEffectivePluginActivationState({
|
||||||
|
id: record.id,
|
||||||
|
origin: record.origin,
|
||||||
|
config: normalizedPlugins,
|
||||||
|
rootConfig: params.cfg,
|
||||||
|
});
|
||||||
|
if (!activationState.activated) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const relativePath of settingsFiles) {
|
||||||
|
const bundleSettings = loadBundleSettingsFile({
|
||||||
|
rootDir: record.rootDir,
|
||||||
|
relativePath,
|
||||||
|
});
|
||||||
|
if (!bundleSettings) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
snapshot = applyMergePatch(snapshot, bundleSettings) as PiSettingsSnapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const embeddedPiMcp = loadEmbeddedPiMcpConfig({
|
||||||
|
workspaceDir,
|
||||||
|
cfg: params.cfg,
|
||||||
|
});
|
||||||
|
for (const diagnostic of embeddedPiMcp.diagnostics) {
|
||||||
|
log.warn(`bundle MCP skipped for ${diagnostic.pluginId}: ${diagnostic.message}`);
|
||||||
|
}
|
||||||
|
if (Object.keys(embeddedPiMcp.mcpServers).length > 0) {
|
||||||
|
snapshot = applyMergePatch(snapshot, {
|
||||||
|
mcpServers: embeddedPiMcp.mcpServers,
|
||||||
|
}) as PiSettingsSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveEmbeddedPiProjectSettingsPolicy(
|
||||||
|
cfg?: OpenClawConfig,
|
||||||
|
): EmbeddedPiProjectSettingsPolicy {
|
||||||
|
const raw = cfg?.agents?.defaults?.embeddedPi?.projectSettingsPolicy;
|
||||||
|
if (raw === "trusted" || raw === "sanitize" || raw === "ignore") {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
return DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildEmbeddedPiSettingsSnapshot(params: {
|
||||||
|
globalSettings: PiSettingsSnapshot;
|
||||||
|
pluginSettings?: PiSettingsSnapshot;
|
||||||
|
projectSettings: PiSettingsSnapshot;
|
||||||
|
policy: EmbeddedPiProjectSettingsPolicy;
|
||||||
|
}): PiSettingsSnapshot {
|
||||||
|
const effectiveProjectSettings =
|
||||||
|
params.policy === "ignore"
|
||||||
|
? {}
|
||||||
|
: params.policy === "sanitize"
|
||||||
|
? sanitizeProjectSettings(params.projectSettings)
|
||||||
|
: params.projectSettings;
|
||||||
|
const withPluginSettings = applyMergePatch(
|
||||||
|
params.globalSettings,
|
||||||
|
sanitizePiSettingsSnapshot(params.pluginSettings ?? {}),
|
||||||
|
) as PiSettingsSnapshot;
|
||||||
|
return applyMergePatch(withPluginSettings, effectiveProjectSettings) as PiSettingsSnapshot;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import path from "node:path";
|
|||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
|
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
|
||||||
|
|
||||||
const { loadEnabledBundlePiSettingsSnapshot } = await import("./pi-project-settings.js");
|
const { loadEnabledBundlePiSettingsSnapshot } = await import("./pi-project-settings-snapshot.js");
|
||||||
|
|
||||||
const tempDirs = createTrackedTempDirs();
|
const tempDirs = createTrackedTempDirs();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
buildEmbeddedPiSettingsSnapshot,
|
buildEmbeddedPiSettingsSnapshot,
|
||||||
DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY,
|
DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY,
|
||||||
resolveEmbeddedPiProjectSettingsPolicy,
|
resolveEmbeddedPiProjectSettingsPolicy,
|
||||||
} from "./pi-project-settings.js";
|
} from "./pi-project-settings-snapshot.js";
|
||||||
|
|
||||||
type EmbeddedPiSettingsArgs = Parameters<typeof buildEmbeddedPiSettingsSnapshot>[0];
|
type EmbeddedPiSettingsArgs = Parameters<typeof buildEmbeddedPiSettingsSnapshot>[0];
|
||||||
|
|
||||||
|
|||||||
@@ -1,163 +1,23 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { SettingsManager } from "@mariozechner/pi-coding-agent";
|
import { SettingsManager } from "@mariozechner/pi-coding-agent";
|
||||||
import { applyMergePatch } from "../config/merge-patch.js";
|
|
||||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
|
||||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
||||||
import type { BundleMcpServerConfig } from "../plugins/bundle-mcp.js";
|
|
||||||
import {
|
import {
|
||||||
normalizePluginsConfig,
|
buildEmbeddedPiSettingsSnapshot,
|
||||||
resolveEffectivePluginActivationState,
|
loadEnabledBundlePiSettingsSnapshot,
|
||||||
} from "../plugins/config-state.js";
|
resolveEmbeddedPiProjectSettingsPolicy,
|
||||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
} from "./pi-project-settings-snapshot.js";
|
||||||
import { isRecord } from "../utils.js";
|
|
||||||
import { loadEmbeddedPiMcpConfig } from "./embedded-pi-mcp.js";
|
|
||||||
import { applyPiCompactionSettingsFromConfig } from "./pi-settings.js";
|
import { applyPiCompactionSettingsFromConfig } from "./pi-settings.js";
|
||||||
|
|
||||||
const log = createSubsystemLogger("embedded-pi-settings");
|
export {
|
||||||
|
buildEmbeddedPiSettingsSnapshot,
|
||||||
export const DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY = "sanitize";
|
DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY,
|
||||||
export const SANITIZED_PROJECT_PI_KEYS = ["shellPath", "shellCommandPrefix"] as const;
|
loadEnabledBundlePiSettingsSnapshot,
|
||||||
|
resolveEmbeddedPiProjectSettingsPolicy,
|
||||||
export type EmbeddedPiProjectSettingsPolicy = "trusted" | "sanitize" | "ignore";
|
SANITIZED_PROJECT_PI_KEYS,
|
||||||
|
} from "./pi-project-settings-snapshot.js";
|
||||||
type PiSettingsSnapshot = ReturnType<SettingsManager["getGlobalSettings"]> & {
|
export type {
|
||||||
mcpServers?: Record<string, BundleMcpServerConfig>;
|
EmbeddedPiProjectSettingsPolicy,
|
||||||
};
|
PiSettingsSnapshot,
|
||||||
|
} from "./pi-project-settings-snapshot.js";
|
||||||
function sanitizePiSettingsSnapshot(settings: PiSettingsSnapshot): PiSettingsSnapshot {
|
|
||||||
const sanitized = { ...settings };
|
|
||||||
// Never allow plugin or workspace-local settings to override shell execution behavior.
|
|
||||||
for (const key of SANITIZED_PROJECT_PI_KEYS) {
|
|
||||||
delete sanitized[key];
|
|
||||||
}
|
|
||||||
return sanitized;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeProjectSettings(settings: PiSettingsSnapshot): PiSettingsSnapshot {
|
|
||||||
return sanitizePiSettingsSnapshot(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadBundleSettingsFile(params: {
|
|
||||||
rootDir: string;
|
|
||||||
relativePath: string;
|
|
||||||
}): PiSettingsSnapshot | null {
|
|
||||||
const absolutePath = path.join(params.rootDir, params.relativePath);
|
|
||||||
const opened = openBoundaryFileSync({
|
|
||||||
absolutePath,
|
|
||||||
rootPath: params.rootDir,
|
|
||||||
boundaryLabel: "plugin root",
|
|
||||||
rejectHardlinks: true,
|
|
||||||
});
|
|
||||||
if (!opened.ok) {
|
|
||||||
log.warn(`skipping unsafe bundle settings file: ${absolutePath}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const raw = JSON.parse(fs.readFileSync(opened.fd, "utf-8")) as unknown;
|
|
||||||
if (!isRecord(raw)) {
|
|
||||||
log.warn(`skipping bundle settings file with non-object JSON: ${absolutePath}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return sanitizePiSettingsSnapshot(raw as PiSettingsSnapshot);
|
|
||||||
} catch (error) {
|
|
||||||
log.warn(`failed to parse bundle settings file ${absolutePath}: ${String(error)}`);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
fs.closeSync(opened.fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadEnabledBundlePiSettingsSnapshot(params: {
|
|
||||||
cwd: string;
|
|
||||||
cfg?: OpenClawConfig;
|
|
||||||
}): PiSettingsSnapshot {
|
|
||||||
const workspaceDir = params.cwd.trim();
|
|
||||||
if (!workspaceDir) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const registry = loadPluginManifestRegistry({
|
|
||||||
workspaceDir,
|
|
||||||
config: params.cfg,
|
|
||||||
});
|
|
||||||
if (registry.plugins.length === 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedPlugins = normalizePluginsConfig(params.cfg?.plugins);
|
|
||||||
let snapshot: PiSettingsSnapshot = {};
|
|
||||||
|
|
||||||
for (const record of registry.plugins) {
|
|
||||||
const settingsFiles = record.settingsFiles ?? [];
|
|
||||||
if (record.format !== "bundle" || settingsFiles.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const activationState = resolveEffectivePluginActivationState({
|
|
||||||
id: record.id,
|
|
||||||
origin: record.origin,
|
|
||||||
config: normalizedPlugins,
|
|
||||||
rootConfig: params.cfg,
|
|
||||||
});
|
|
||||||
if (!activationState.activated) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const relativePath of settingsFiles) {
|
|
||||||
const bundleSettings = loadBundleSettingsFile({
|
|
||||||
rootDir: record.rootDir,
|
|
||||||
relativePath,
|
|
||||||
});
|
|
||||||
if (!bundleSettings) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
snapshot = applyMergePatch(snapshot, bundleSettings) as PiSettingsSnapshot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const embeddedPiMcp = loadEmbeddedPiMcpConfig({
|
|
||||||
workspaceDir,
|
|
||||||
cfg: params.cfg,
|
|
||||||
});
|
|
||||||
for (const diagnostic of embeddedPiMcp.diagnostics) {
|
|
||||||
log.warn(`bundle MCP skipped for ${diagnostic.pluginId}: ${diagnostic.message}`);
|
|
||||||
}
|
|
||||||
if (Object.keys(embeddedPiMcp.mcpServers).length > 0) {
|
|
||||||
snapshot = applyMergePatch(snapshot, {
|
|
||||||
mcpServers: embeddedPiMcp.mcpServers,
|
|
||||||
}) as PiSettingsSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveEmbeddedPiProjectSettingsPolicy(
|
|
||||||
cfg?: OpenClawConfig,
|
|
||||||
): EmbeddedPiProjectSettingsPolicy {
|
|
||||||
const raw = cfg?.agents?.defaults?.embeddedPi?.projectSettingsPolicy;
|
|
||||||
if (raw === "trusted" || raw === "sanitize" || raw === "ignore") {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
return DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildEmbeddedPiSettingsSnapshot(params: {
|
|
||||||
globalSettings: PiSettingsSnapshot;
|
|
||||||
pluginSettings?: PiSettingsSnapshot;
|
|
||||||
projectSettings: PiSettingsSnapshot;
|
|
||||||
policy: EmbeddedPiProjectSettingsPolicy;
|
|
||||||
}): PiSettingsSnapshot {
|
|
||||||
const effectiveProjectSettings =
|
|
||||||
params.policy === "ignore"
|
|
||||||
? {}
|
|
||||||
: params.policy === "sanitize"
|
|
||||||
? sanitizeProjectSettings(params.projectSettings)
|
|
||||||
: params.projectSettings;
|
|
||||||
const withPluginSettings = applyMergePatch(
|
|
||||||
params.globalSettings,
|
|
||||||
sanitizePiSettingsSnapshot(params.pluginSettings ?? {}),
|
|
||||||
) as PiSettingsSnapshot;
|
|
||||||
return applyMergePatch(withPluginSettings, effectiveProjectSettings) as PiSettingsSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createEmbeddedPiSettingsManager(params: {
|
export function createEmbeddedPiSettingsManager(params: {
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user