import { isDeepStrictEqual } from "node:util"; import { isRecord } from "../utils.js"; import { applyMergePatch } from "./merge-patch.js"; import { isBlockedObjectKey } from "./prototype-keys.js"; import type { OpenClawConfig } from "./types.js";
const OPEN_DM_POLICY_ALLOW_FROM_RE =
/^(?<policyPath>[a-z0-9_.-]+)\s*=\s*"open"\s+requires\s+(?<allowPath>[a-z0-9_.-]+)(?:\s+\(or\s+[a-z0-9_.-]+\))?\s+to include "\*"$/i;
function cloneUnknown<T>(value: T): T { return structuredClone(value);
}
export function createMergePatch(base: unknown, target: unknown): unknown { if (!isRecord(base) || !isRecord(target)) { return cloneUnknown(target);
}
const patch: Record<string, unknown> = {}; const keys = new Set([...Object.keys(base), ...Object.keys(target)]); for (const key of keys) { const hasBase = key in base; const hasTarget = key in target; if (!hasTarget) {
patch[key] = null; continue;
} const targetValue = target[key]; if (!hasBase) {
patch[key] = cloneUnknown(targetValue); continue;
} const baseValue = base[key]; if (isRecord(baseValue) && isRecord(targetValue)) { const childPatch = createMergePatch(baseValue, targetValue); if (isRecord(childPatch) && Object.keys(childPatch).length === 0) { continue;
}
patch[key] = childPatch; continue;
} if (!isDeepStrictEqual(baseValue, targetValue)) {
patch[key] = cloneUnknown(targetValue);
}
} return patch;
}
export function projectSourceOntoRuntimeShape(source: unknown, runtime: unknown): unknown { if (!isRecord(source) || !isRecord(runtime)) { return cloneUnknown(source);
}
const next: Record<string, unknown> = {}; for (const [key, sourceValue] of Object.entries(source)) { if (!(key in runtime)) {
next[key] = cloneUnknown(sourceValue); continue;
}
next[key] = projectSourceOntoRuntimeShape(sourceValue, runtime[key]);
} return next;
}
function hasOwnIncludeKey(value: unknown): value is Record<string, unknown> { return isRecord(value) && Object.prototype.hasOwnProperty.call(value, "$include");
}
function collectIncludeOwnedPaths(value: unknown, path: string[] = []): string[][] { if (!isRecord(value)) { return [];
} if (hasOwnIncludeKey(value)) { return [path];
} return Object.entries(value).flatMap(([key, child]) =>
collectIncludeOwnedPaths(child, [...path, key]),
);
}
function getPathValue(value: unknown, path: string[]): unknown {
let current = value; for (const segment of path) { if (!isRecord(current)) { return undefined;
}
current = current[segment];
} return current;
}
function preserveUntouchedIncludes(params: {
patch: unknown;
rootAuthoredConfig: unknown;
persistedCandidate: unknown;
}): unknown {
let next = params.persistedCandidate; for (const includePath of collectIncludeOwnedPaths(params.rootAuthoredConfig)) { if (patchTouchesPath(params.patch, includePath)) { thrownew Error(
`Config write would flatten $include-owned config at ${formatConfigPath(
includePath,
)}; edit that include file directly or remove the $include first.`,
);
}
next = setPathValue(next, includePath, getPathValue(params.rootAuthoredConfig, includePath));
} return next;
}
return [
`Config validation failed: ${pathLabel}`, "",
`Configuration mismatch: ${policyPath} is "open", but ${allowPath} does not include "*".`, "", "Fix with:",
` openclaw config set ${allowPath} '["*"]'`, "", "Or switch policy:",
` openclaw config set ${policyPath} "pairing"`,
].join("\n");
}
function isNumericPathSegment(raw: string): boolean { return /^[0-9]+$/.test(raw);
}
function isWritePlainObject(value: unknown): value is Record<string, unknown> { returnBoolean(value) && typeof value === "object" && !Array.isArray(value);
}
function coerceConfig(value: unknown): OpenClawConfig { if (!value || typeof value !== "object" || Array.isArray(value)) { return {};
} return value as OpenClawConfig;
}
export function collectChangedPaths(
base: unknown,
target: unknown,
path: string,
output: Set<string>,
): void { if (Array.isArray(base) && Array.isArray(target)) { const max = Math.max(base.length, target.length); for (let index = 0; index < max; index += 1) { const childPath = path ? `${path}[${index}]` : `[${index}]`; if (index >= base.length || index >= target.length) {
output.add(childPath); continue;
}
collectChangedPaths(base[index], target[index], childPath, output);
} return;
} if (isRecord(base) && isRecord(target)) { const keys = new Set([...Object.keys(base), ...Object.keys(target)]); for (const key of keys) { const childPath = path ? `${path}.${key}` : key; const hasBase = key in base; const hasTarget = key in target; if (!hasTarget || !hasBase) {
output.add(childPath); continue;
}
collectChangedPaths(base[key], target[key], childPath, output);
} return;
} if (!isDeepStrictEqual(base, target)) {
output.add(path);
}
}
function parentPath(value: string): string { if (!value) { return"";
} if (value.endsWith("]")) { const index = value.lastIndexOf("["); return index > 0 ? value.slice(0, index) : "";
} const index = value.lastIndexOf("."); return index >= 0 ? value.slice(0, index) : "";
}
function isPathChanged(path: string, changedPaths: Set<string>): boolean { if (changedPaths.has(path)) { returntrue;
}
let current = parentPath(path); while (current) { if (changedPaths.has(current)) { returntrue;
}
current = parentPath(current);
} return changedPaths.has("");
}
export function restoreEnvRefsFromMap(
value: unknown,
path: string,
envRefMap: Map<string, string>,
changedPaths: Set<string>,
): unknown { if (typeof value === "string") { if (!isPathChanged(path, changedPaths)) { const original = envRefMap.get(path); if (original !== undefined) { return original;
}
} return value;
} if (Array.isArray(value)) {
let changed = false; const next = value.map((item, index) => { const updated = restoreEnvRefsFromMap(item, `${path}[${index}]`, envRefMap, changedPaths); if (updated !== item) {
changed = true;
} return updated;
}); return changed ? next : value;
} if (isRecord(value)) {
let changed = false; const next: Record<string, unknown> = {}; for (const [key, child] of Object.entries(value)) { const childPath = path ? `${path}.${key}` : key; const updated = restoreEnvRefsFromMap(child, childPath, envRefMap, changedPaths); if (updated !== child) {
changed = true;
}
next[key] = updated;
} return changed ? next : value;
} return value;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.