openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
229 lines (228 loc) • 8.71 kB
JavaScript
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { d as isSecretRef } from "./types.secrets-kC0nOetj.js";
import { t as VERSION } from "./version-v1kuAkGj.js";
import { r as shouldWarnOnTouchedVersion } from "./version-BXayNebU.js";
import { t as isSensitiveConfigPath } from "./sensitive-paths-uMRji-4g.js";
import { n as formatConfigIssueLines } from "./issue-format-CQvxWNW8.js";
import path from "node:path";
import JSON5 from "json5";
//#region src/config/issue-location.ts
function skipTrivia(raw, cursor) {
while (cursor.pos < raw.length) {
const char = raw[cursor.pos];
if (/\s/u.test(char ?? "")) {
cursor.pos++;
continue;
}
if (char === "/" && raw[cursor.pos + 1] === "/") {
cursor.pos += 2;
while (cursor.pos < raw.length && !/[\n\r\u2028\u2029]/u.test(raw[cursor.pos] ?? "")) cursor.pos++;
continue;
}
if (char === "/" && raw[cursor.pos + 1] === "*") {
const close = raw.indexOf("*/", cursor.pos + 2);
cursor.pos = close === -1 ? raw.length : close + 2;
continue;
}
return;
}
}
function scanQuoted(raw, cursor) {
const quote = raw[cursor.pos++];
while (cursor.pos < raw.length) {
const char = raw[cursor.pos++];
if (char === "\\") cursor.pos++;
else if (char === quote) return;
}
}
function consume(raw, cursor, expected) {
skipTrivia(raw, cursor);
if (raw[cursor.pos] !== expected) return false;
cursor.pos++;
return true;
}
function readObjectKey(raw, cursor) {
skipTrivia(raw, cursor);
const start = cursor.pos;
const char = raw[cursor.pos];
if (char === "\"" || char === "'") scanQuoted(raw, cursor);
else while (cursor.pos < raw.length && !/[\s:]/u.test(raw[cursor.pos] ?? "") && !(raw[cursor.pos] === "/" && /[/*]/u.test(raw[cursor.pos + 1] ?? ""))) cursor.pos++;
if (cursor.pos === start) return null;
const token = raw.slice(start, cursor.pos);
try {
if (char === "\"" || char === "'") {
const parsed = JSON5.parse(token);
return typeof parsed === "string" ? parsed : null;
}
const parsed = JSON5.parse(`{${token}:null}`);
return Object.keys(parsed)[0] ?? null;
} catch {
return null;
}
}
function skipValue(raw, cursor) {
skipTrivia(raw, cursor);
const char = raw[cursor.pos];
if (char === "\"" || char === "'") {
scanQuoted(raw, cursor);
return;
}
if (char === "{") {
cursor.pos++;
while (cursor.pos < raw.length) {
skipTrivia(raw, cursor);
if (raw[cursor.pos] === "}") {
cursor.pos++;
return;
}
if (readObjectKey(raw, cursor) === null || !consume(raw, cursor, ":")) return;
skipValue(raw, cursor);
skipTrivia(raw, cursor);
if (raw[cursor.pos] === ",") cursor.pos++;
}
return;
}
if (char === "[") {
cursor.pos++;
while (cursor.pos < raw.length) {
skipTrivia(raw, cursor);
if (raw[cursor.pos] === "]") {
cursor.pos++;
return;
}
skipValue(raw, cursor);
skipTrivia(raw, cursor);
if (raw[cursor.pos] === ",") cursor.pos++;
}
return;
}
while (cursor.pos < raw.length && !/[,}\]\s]/u.test(raw[cursor.pos] ?? "") && !(raw[cursor.pos] === "/" && /[/*]/u.test(raw[cursor.pos + 1] ?? ""))) cursor.pos++;
}
function locateValueOffset(raw, cursor, segments, depth) {
const segment = segments[depth];
const isLeaf = depth === segments.length - 1;
if (typeof segment === "number") {
if (!consume(raw, cursor, "[")) return;
for (let index = 0; cursor.pos < raw.length; index++) {
skipTrivia(raw, cursor);
if (raw[cursor.pos] === "]") return;
if (index === segment) return isLeaf ? cursor.pos : locateValueOffset(raw, cursor, segments, depth + 1);
skipValue(raw, cursor);
if (!consume(raw, cursor, ",")) return;
}
return;
}
if (!consume(raw, cursor, "{")) return;
let lastMatch;
while (cursor.pos < raw.length) {
skipTrivia(raw, cursor);
if (raw[cursor.pos] === "}") return lastMatch;
const key = readObjectKey(raw, cursor);
if (key === null || !consume(raw, cursor, ":")) return;
skipTrivia(raw, cursor);
const valueStart = cursor.pos;
if (key === segment) lastMatch = isLeaf ? valueStart : locateValueOffset(raw, { pos: valueStart }, segments, depth + 1);
cursor.pos = valueStart;
skipValue(raw, cursor);
skipTrivia(raw, cursor);
if (raw[cursor.pos] === ",") {
cursor.pos++;
continue;
}
return lastMatch;
}
return lastMatch;
}
function lineAtOffset(raw, offset) {
let line = 1;
for (let index = 0; index < offset; index++) {
const char = raw[index];
if (char === "\r") {
line++;
if (raw[index + 1] === "\n") index++;
} else if (char === "\n" || char === "\u2028" || char === "\u2029") line++;
}
return line;
}
function formatConfigIssuePath(segments) {
return segments.reduce((result, segment) => typeof segment === "number" ? `${result}[${segment}]` : result ? `${result}.${segment}` : segment, "");
}
function resolveConfigValueAtPath(root, segments) {
let current = root;
for (const segment of segments) {
if (typeof segment === "number") {
if (!Array.isArray(current) || segment >= current.length) return;
current = current[segment];
continue;
}
if (!current || typeof current !== "object" || Array.isArray(current)) return;
current = current[segment];
}
return current;
}
function stringifyReceivedValue(value) {
if (value === void 0) return null;
if (typeof value === "number" && (!Number.isFinite(value) || Object.is(value, -0))) return Object.is(value, -0) ? "-0" : String(value);
try {
const serialized = JSON.stringify(value);
if (serialized === void 0) return null;
return serialized.length > 160 ? `${truncateUtf16Safe(serialized, 157)}...` : serialized;
} catch {
return null;
}
}
function isPluginOwnedConfigPath(pathValue, pathSegments) {
if (pathSegments) return pathSegments[0] === "channels" || pathSegments[0] === "plugins" && pathSegments[1] === "entries" && pathSegments[3] === "config";
return pathValue.startsWith("channels.") || /^plugins\.entries\.[^.]+\.config(?:\.|$)/.test(pathValue);
}
function shouldOmitReceivedValue(pathValue, value, pathSegments) {
return value === void 0 || isSecretRef(value) || isSensitiveConfigPath(pathValue) || isPluginOwnedConfigPath(pathValue, pathSegments) || typeof value === "object" && value !== null || stringifyReceivedValue(value) === null;
}
function appendReceivedValueHint(message, pathValue, value, pathSegments) {
if (shouldOmitReceivedValue(pathValue, value, pathSegments) || message.toLowerCase().includes("got:") || /\breceived\b/i.test(message)) return message;
const label = stringifyReceivedValue(value);
return label ? `${message}, got: ${label}` : message;
}
function resolveConfigIssueLineInRaw(raw, segments) {
if (segments.length === 0 || raw.trim().length === 0) return;
const offset = locateValueOffset(raw, { pos: 0 }, segments, 0);
return offset === void 0 ? void 0 : lineAtOffset(raw, offset);
}
function attachConfigIssueDiagnostics(issues, params) {
const raw = typeof params.raw === "string" ? params.raw : null;
const sourceFile = typeof params.configPath === "string" && params.configPath.trim() ? path.basename(params.configPath) : "openclaw.json";
return issues.map((issue) => {
const segments = issue.pathSegments;
if (!segments || segments.length === 0) return issue;
const literalValue = resolveConfigValueAtPath(params.parsed, segments);
const effectiveValue = resolveConfigValueAtPath(params.effective, segments);
const line = raw === null ? void 0 : resolveConfigIssueLineInRaw(raw, segments);
const canShowReceivedValue = line !== void 0 && Object.is(literalValue, effectiveValue);
const message = params.includeReceivedValueHint && canShowReceivedValue ? appendReceivedValueHint(issue.message, issue.path, effectiveValue, segments) : issue.message;
return {
...issue,
path: params.formatPathForDisplay ? formatConfigIssuePath(segments) : issue.path,
message,
...line === void 0 ? {} : {
line,
sourceFile
}
};
});
}
/** Render invalid config issues with source locations, received values, and version-skew advice. */
function renderConfigValidationIssueLines(snapshot, marker = "-") {
const issues = attachConfigIssueDiagnostics(snapshot.issues, {
raw: snapshot.raw,
parsed: snapshot.parsed,
effective: snapshot.sourceConfig,
configPath: snapshot.path,
formatPathForDisplay: true,
includeReceivedValueHint: true
});
const lines = formatConfigIssueLines(issues, marker, { normalizeRoot: true });
const touchedVersion = snapshot.sourceConfig.meta?.lastTouchedVersion;
return shouldWarnOnTouchedVersion(VERSION, touchedVersion) ? [...lines, `Config was last written by OpenClaw ${touchedVersion}, but you are running ${VERSION} — upgrade or re-run setup.`] : lines;
}
//#endregion
export { renderConfigValidationIssueLines as t };