openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
230 lines (229 loc) • 9.49 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-1VuiLaaR.mjs";
import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs";
import { u as isUnresolvedSecretInputError } from "./types.secrets-DD-Q2SS_.mjs";
import { u as findUninspectedPluginDiagnostic } from "./discovery-D42cUZXk.mjs";
import { c as loadManifestMetadataSnapshot } from "./manifest-contract-eligibility-P_yAPTzd.mjs";
import { i as getBundledChannelSetupPlugin, n as getBundledChannelPlugin } from "./bundled-B97xNBpl.mjs";
import { t as applyPluginDoctorCompatibilitySequence } from "./doctor-compatibility-migration-BSMtompL.mjs";
import { n as getLoadedChannelPlugin } from "./registry-Bcrk1FVy.mjs";
import { t as listDoctorConfiguredChannelIds } from "./configured-channel-ids-CwITTl83.mjs";
import { n as resolveReadOnlyChannelPluginsForConfig } from "./read-only-DbPcZnSt.mjs";
//#region src/commands/doctor/shared/channel-doctor.ts
const channelDoctorFunctionKeys = /* @__PURE__ */ new Set([
"normalizeCompatibilityConfig",
"collectPreviewWarnings",
"collectMutableAllowlistWarnings",
"repairConfig",
"runConfigSequence",
"cleanStaleConfig",
"collectEmptyAllowlistExtraWarnings",
"shouldSkipDefaultEmptyGroupAllowlistWarning"
]);
const channelDoctorBooleanKeys = /* @__PURE__ */ new Set(["groupAllowFromFallbackToAllowFrom", "warnOnEmptyGroupSenderAllowlist"]);
const channelDoctorEnumValues = {
dmAllowFromMode: /* @__PURE__ */ new Set([
"topOnly",
"topOrNested",
"nestedOnly"
]),
groupModel: /* @__PURE__ */ new Set([
"sender",
"route",
"hybrid"
])
};
function collectConfiguredChannelIds(cfg) {
return listDoctorConfiguredChannelIds(cfg, {
configEntryPolicy: "enabled",
skipWhenPluginsDisabled: true,
excludeExplicitlyDisabled: true,
sort: "codepoint"
}).filter((channelId) => !isChannelDoctorBlockedByConfig(channelId, cfg));
}
function isChannelDoctorBlockedByConfig(channelId, cfg) {
if (cfg.plugins?.enabled === false) return true;
const normalizedChannelId = normalizeOptionalLowercaseString(channelId) ?? channelId;
if (cfg.plugins?.entries?.[normalizedChannelId]?.enabled === false) return true;
return asOptionalRecord(cfg.channels?.[normalizedChannelId])?.enabled === false;
}
function safelyResolveChannelPlugin(id, resolve) {
try {
return resolve(id);
} catch {
return;
}
}
function listReadOnlyChannelDoctorsById(context) {
try {
return new Map(resolveReadOnlyChannelPluginsForConfig(context.cfg, {
...context.env ? { env: context.env } : {},
includePersistedAuthState: false,
includeSetupFallbackPlugins: true
}).plugins.map(({ id, doctor }) => [id, doctor]));
} catch {
return /* @__PURE__ */ new Map();
}
}
function mergeDoctorAdapters(adapters) {
const merged = {};
for (const adapter of adapters) {
if (!adapter) continue;
for (const [key, value] of Object.entries(adapter)) if (merged[key] === void 0 && isValidChannelDoctorAdapterValue(key, value)) merged[key] = value;
}
return Object.keys(merged).length > 0 ? merged : void 0;
}
function isValidChannelDoctorAdapterValue(key, value) {
if (channelDoctorFunctionKeys.has(key)) return typeof value === "function";
if (channelDoctorBooleanKeys.has(key)) return typeof value === "boolean";
const enumValues = channelDoctorEnumValues[key];
if (enumValues) return typeof value === "string" && enumValues.has(value);
if (key === "legacyConfigRules") return Array.isArray(value);
return false;
}
function listChannelDoctorEntries(context, channelIds = collectConfiguredChannelIds(context.cfg), readOnlyDoctorsById) {
const selectedIds = new Set(channelIds.filter((id) => !isChannelDoctorBlockedByConfig(id, context.cfg)));
if (selectedIds.size === 0) return [];
const doctors = readOnlyDoctorsById ?? listReadOnlyChannelDoctorsById(context);
const entries = [];
for (const id of selectedIds) {
const doctor = mergeDoctorAdapters([
doctors.get(id),
safelyResolveChannelPlugin(id, getLoadedChannelPlugin)?.doctor,
safelyResolveChannelPlugin(id, getBundledChannelSetupPlugin)?.doctor,
safelyResolveChannelPlugin(id, getBundledChannelPlugin)?.doctor
]);
if (!doctor) continue;
entries.push({
id,
doctor
});
}
return entries;
}
function preserveUnavailableChannelConfig(context) {
if (!context.cfg.plugins?.load?.paths?.length) return;
const warning = findUninspectedPluginDiagnostic(loadManifestMetadataSnapshot({
config: context.cfg,
env: context.env
}).diagnostics);
return warning ? {
config: context.cfg,
changes: [],
warnings: [warning.message]
} : void 0;
}
async function collectChannelDoctorMutations(context, mutate, channelIds) {
const preserved = preserveUnavailableChannelConfig(context);
if (preserved) return [preserved];
const mutations = [];
let nextCfg = context.cfg;
for (const { doctor } of listChannelDoctorEntries(context, channelIds)) {
const mutation = await mutate(doctor, nextCfg);
if (mutation?.changes.length) {
mutations.push(mutation);
nextCfg = mutation.config;
} else if (mutation?.warnings?.length) mutations.push({
config: nextCfg,
changes: [],
warnings: mutation.warnings
});
}
return mutations;
}
/** Build cached empty-allowlist hooks backed by channel doctor adapters. */
function createChannelDoctorEmptyAllowlistPolicyHooks(context) {
const readOnlyDoctorsById = listReadOnlyChannelDoctorsById(context);
const entriesByChannel = /* @__PURE__ */ new Map();
const entriesForChannel = (channelName) => {
const existing = entriesByChannel.get(channelName);
if (existing) return existing;
const entries = listChannelDoctorEntries(context, [channelName], readOnlyDoctorsById);
entriesByChannel.set(channelName, entries);
return entries;
};
return {
extraWarningsForAccount: (params) => entriesForChannel(params.channelName).flatMap((entry) => entry.doctor.collectEmptyAllowlistExtraWarnings?.(params) ?? []),
shouldSkipDefaultEmptyGroupAllowlistWarning: (params) => entriesForChannel(params.channelName).some((entry) => entry.doctor.shouldSkipDefaultEmptyGroupAllowlistWarning?.(params) === true)
};
}
/** Run interactive/non-interactive channel setup repair sequences and collect notes. */
async function runChannelDoctorConfigSequences(params) {
const preserved = preserveUnavailableChannelConfig(params);
if (preserved) return {
changeNotes: [],
warningNotes: preserved.warnings ?? []
};
const changeNotes = [];
const infoNotes = [];
const warningNotes = [];
for (const entry of listChannelDoctorEntries(params)) {
const result = await entry.doctor.runConfigSequence?.(params);
if (!result) continue;
changeNotes.push(...result.changeNotes);
infoNotes.push(...result.infoNotes ?? []);
warningNotes.push(...result.warningNotes);
}
return {
changeNotes,
warningNotes,
...infoNotes.length > 0 ? { infoNotes } : {}
};
}
/** Collect compatibility migrations from configured channel doctor adapters in order. */
function collectChannelDoctorCompatibilityMutations(cfg, options = {}) {
const preserved = preserveUnavailableChannelConfig({
cfg,
env: options.env
});
if (preserved) return [preserved];
const mutation = applyPluginDoctorCompatibilitySequence(cfg, listChannelDoctorEntries({
cfg,
env: options.env
}).map(({ id, doctor }) => ({
pluginId: id,
normalizeCompatibilityConfig: doctor.normalizeCompatibilityConfig?.bind(doctor)
})));
return mutation.changes.length || mutation.warnings?.length ? [mutation] : [];
}
/** Collect stale channel config cleanup mutations from configured channel doctor adapters. */
async function collectChannelDoctorStaleConfigMutations(cfg, options = {}) {
return collectChannelDoctorMutations({
cfg,
env: options.env
}, (doctor, nextCfg) => doctor.cleanStaleConfig?.({ cfg: nextCfg }), options.channelIds);
}
/** Collect channel-specific doctor preview warnings for configured channels. */
async function collectChannelDoctorPreviewWarnings(params) {
const warnings = [];
for (const entry of listChannelDoctorEntries(params)) {
let lines;
try {
lines = await entry.doctor.collectPreviewWarnings?.(params);
} catch (error) {
if (!isUnresolvedSecretInputError(error)) throw error;
warnings.push(`- channels.${entry.id}: configured SecretRef at ${error.path} is unavailable in doctor preview; skipping secret-backed channel preview checks.`);
continue;
}
if (lines?.length) warnings.push(...lines);
}
return warnings;
}
/** Collect warnings for mutable channel allowlists that doctor cannot safely edit. */
async function collectChannelDoctorMutableAllowlistWarnings(params) {
const warnings = [];
for (const entry of listChannelDoctorEntries(params)) {
const lines = await entry.doctor.collectMutableAllowlistWarnings?.(params);
if (lines?.length) warnings.push(...lines);
}
return warnings;
}
/** Collect channel repair mutations and warning-only repair results from doctor adapters. */
async function collectChannelDoctorRepairMutations(params) {
return collectChannelDoctorMutations(params, (doctor, nextCfg) => doctor.repairConfig?.({
cfg: nextCfg,
doctorFixCommand: params.doctorFixCommand,
...params.env ? { env: params.env } : {}
}));
}
//#endregion
export { collectChannelDoctorStaleConfigMutations as a, collectChannelDoctorRepairMutations as i, collectChannelDoctorMutableAllowlistWarnings as n, createChannelDoctorEmptyAllowlistPolicyHooks as o, collectChannelDoctorPreviewWarnings as r, runChannelDoctorConfigSequences as s, collectChannelDoctorCompatibilityMutations as t };