openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
496 lines (495 loc) • 24.2 kB
JavaScript
import { s as isRecord } from "./runtime-doctor-migrations-DJDQaWC5.js";
import { h as normalizeOptionalString } from "./channel-doctor-helpers-CKvCIMDR.js";
import { t as defineChannelAliasMigration } from "./channel-alias-migration-Du1swkf7.js";
import { n as normalizeAccountId, t as DEFAULT_ACCOUNT_ID } from "./fs-safe-defaults-BbLMaB4h.js";
import "./string-coerce-runtime-B3dXyeph.js";
import { l as escapeRegExp } from "./utils-wQdDu1q1.js";
import { isIP } from "node:net";
[{
channel: "signal",
peerKinds: /* @__PURE__ */ new Set(["group"]),
span: "segment",
unscoped: true
}, {
channel: "matrix",
peerKinds: /* @__PURE__ */ new Set(["channel", "group"]),
span: "tail",
unscoped: true
}].flatMap((descriptor) => [...descriptor.peerKinds].map((peerKind) => {
const prefix = `${escapeRegExp(descriptor.channel)}:${escapeRegExp(peerKind)}:`;
return {
span: descriptor.span,
pattern: new RegExp(descriptor.span === "segment" ? `(^|:)${prefix}([^:]+)` : `^(?:agent:[^:]*:)+:*${prefix}`, descriptor.span === "segment" ? "gi" : "i"),
unscopedPattern: descriptor.span === "tail" && descriptor.unscoped ? new RegExp(`^${prefix}`, "i") : void 0
};
}));
//#endregion
//#region extensions/signal/src/transport-url.ts
function normalizeSignalTransportUrl(value) {
const trimmed = value.trim();
if (!trimmed) throw new Error("Signal transport URL is required");
if (/^https?:/i.test(trimmed) && !/^https?:\/\/[^/]/i.test(trimmed)) throw new Error("Signal transport URL has a malformed HTTP scheme");
const explicitScheme = /^([a-z][a-z0-9+.-]*):\/\//i.exec(trimmed)?.[1]?.toLowerCase();
if (explicitScheme && explicitScheme !== "http" && explicitScheme !== "https") throw new Error(`Signal transport URL unsupported protocol: ${explicitScheme}:`);
const parsed = new URL(/^https?:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`Signal transport URL unsupported protocol: ${parsed.protocol}`);
if (parsed.username || parsed.password) throw new Error("Signal transport URL must not include credentials");
const pathname = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "");
return `${parsed.protocol}//${parsed.host}${pathname}`;
}
function normalizeSignalTransportHost(host) {
const trimmedHost = host.trim();
const hasOpeningBracket = trimmedHost.startsWith("[");
if (hasOpeningBracket !== trimmedHost.endsWith("]")) throw new Error("Signal transport host has mismatched IPv6 brackets");
const normalizedHost = hasOpeningBracket ? trimmedHost.slice(1, -1) : trimmedHost;
if (!normalizedHost || /[\s/\\?#@]/.test(normalizedHost)) throw new Error("Signal transport host must be a hostname or IP address");
if (isIP(normalizedHost) === 0) {
const hostname = normalizedHost.endsWith(".") ? normalizedHost.slice(0, -1) : normalizedHost;
const labels = hostname.split(".");
if (hostname.length > 253 || labels.some((label) => !label || label.length > 63 || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label))) throw new Error("Signal transport host must be a hostname or IP address");
}
return normalizedHost;
}
function buildSignalTransportHttpUrl(host, port) {
const normalizedHost = normalizeSignalTransportHost(host);
return normalizeSignalTransportUrl(`http://${normalizedHost.includes(":") ? `[${normalizedHost}]` : normalizedHost}:${port}`);
}
//#endregion
//#region extensions/signal/src/transport-policy.ts
const DEFAULT_SIGNAL_MANAGED_NATIVE_PORT = 8080;
const SIGNAL_LOOPBACK_HOST_ALIASES = /* @__PURE__ */ new Set([
"localhost",
"127.0.0.1",
"::1"
]);
function normalizeSignalEndpointHost(hostname) {
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
return normalized.endsWith(".") ? normalized.slice(0, -1) : normalized;
}
function isSignalLocalEndpointHost(hostname) {
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || hostname === "::" || hostname === "0.0.0.0" || /^127(?:\.\d{1,3}){3}$/.test(hostname) || /^::ffff:(?:127(?:\.\d{1,3}){3}|7f[0-9a-f]{2}:[0-9a-f]{1,4})$/.test(hostname);
}
function isValidSignalManagedNativePort(value) {
return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
}
function allocateSignalManagedNativePort(params) {
if (params.preferredPort !== void 0) {
if (!isValidSignalManagedNativePort(params.preferredPort)) throw new Error("Signal managed native port must be an integer between 1 and 65535.");
if (!params.reservedPorts.has(params.preferredPort)) return params.preferredPort;
}
let port = DEFAULT_SIGNAL_MANAGED_NATIVE_PORT;
while (port <= 65535 && params.reservedPorts.has(port)) port += 1;
if (port > 65535) throw new Error("No available Signal managed native port remains.");
return port;
}
function resolveLocalSignalTransportPort(baseUrl) {
try {
const parsed = new URL(baseUrl);
if (!isSignalLocalEndpointHost(normalizeSignalEndpointHost(parsed.hostname))) return;
if (parsed.port) return Number.parseInt(parsed.port, 10);
return parsed.protocol === "https:" ? 443 : 80;
} catch {
return;
}
}
function isSignalManagedNativeConnectionUrlForBind(transport) {
if (transport.kind !== "managed-native" || !transport.url) return false;
const connectionUrl = new URL(transport.url);
if (connectionUrl.protocol !== "http:") return false;
if ((connectionUrl.port ? Number.parseInt(connectionUrl.port, 10) : 80) !== (transport.httpPort ?? 8080)) return false;
const connectionHost = normalizeSignalEndpointHost(connectionUrl.hostname);
const bindHost = normalizeSignalEndpointHost(transport.httpHost ?? "127.0.0.1");
if (connectionHost === bindHost) return true;
if (bindHost === "0.0.0.0") return connectionHost === "localhost" || /^127(?:\.\d{1,3}){3}$/.test(connectionHost);
if (bindHost === "::") return connectionHost === "localhost" || connectionHost === "::1";
return bindHost === "localhost" && SIGNAL_LOOPBACK_HOST_ALIASES.has(connectionHost) || connectionHost === "localhost" && SIGNAL_LOOPBACK_HOST_ALIASES.has(bindHost);
}
function assignSignalManagedNativePort(transport, httpPort) {
if (!isValidSignalManagedNativePort(httpPort)) throw new Error("Signal managed native port must be an integer between 1 and 65535.");
const connectionUrlValue = transport.url;
if (!connectionUrlValue || !isSignalManagedNativeConnectionUrlForBind(transport)) return {
...transport,
httpPort
};
const connectionUrl = new URL(connectionUrlValue);
connectionUrl.port = String(httpPort);
return {
...transport,
url: normalizeSignalTransportUrl(connectionUrl.toString()),
httpPort
};
}
//#endregion
//#region extensions/signal/src/config-compat.ts
const LEGACY_TRANSPORT_FIELDS = [
"configPath",
"httpUrl",
"httpHost",
"httpPort",
"cliPath",
"autoStart",
"startupTimeoutMs",
"receiveMode",
"ignoreStories"
];
const PENDING_LEGACY_TRANSPORT_WARNING = "- channels.signal: legacy auto transport is ambiguous while its endpoint is unavailable; bring the endpoint online and rerun openclaw doctor --fix, or replace the retired fields with an explicit account-owned transport in openclaw.json.";
const PENDING_LEGACY_INVALID_URL_WARNING = "- channels.signal: legacy httpUrl is invalid; keep the current config, correct httpUrl, then run openclaw doctor --fix.";
const PENDING_LEGACY_INVALID_HOST_WARNING = "- channels.signal: legacy httpHost is invalid; keep the current config, correct httpHost, then run openclaw doctor --fix.";
const PENDING_LEGACY_INVALID_PORT_WARNING = "- channels.signal: legacy httpPort must be an integer between 1 and 65535; correct httpPort, then run openclaw doctor --fix.";
const PENDING_LEGACY_CONTAINER_ACCOUNT_WARNING = "- channels.signal: legacy container transport requires an account number; add channels.signal.account (or the relevant channels.signal.accounts.*.account) and rerun openclaw doctor --fix.";
function isSignalTransportConfig(value) {
if (!isRecord(value)) return false;
if (value.kind === "managed-native") {
if (value.httpPort !== void 0 && !isValidSignalManagedNativePort(value.httpPort)) return false;
if (value.url === void 0) return true;
if (typeof value.url !== "string") return false;
try {
normalizeSignalTransportUrl(value.url);
return true;
} catch {
return false;
}
}
if (value.kind !== "external-native" && value.kind !== "container" || typeof value.url !== "string") return false;
try {
normalizeSignalTransportUrl(value.url);
return true;
} catch {
return false;
}
}
function inherited(entry, parent, key) {
return Object.hasOwn(entry, key) ? entry[key] : parent[key];
}
function legacyBaseUrl(entry, parent) {
const url = normalizeOptionalString(inherited(entry, parent, "httpUrl"));
if (url) return normalizeSignalTransportUrl(url);
const host = normalizeOptionalString(inherited(entry, parent, "httpHost")) ?? "127.0.0.1";
const rawPort = inherited(entry, parent, "httpPort");
return buildSignalTransportHttpUrl(host, typeof rawPort === "number" ? rawPort : 8080);
}
function hasLegacyFields(entry) {
return LEGACY_TRANSPORT_FIELDS.some((field) => Object.hasOwn(entry, field));
}
function wasLegacySignalAccountConfigured(entry, parent) {
return Boolean(normalizeOptionalString(inherited(entry, parent, "account")) || normalizeOptionalString(inherited(entry, parent, "configPath")) || normalizeOptionalString(inherited(entry, parent, "httpUrl")) || normalizeOptionalString(inherited(entry, parent, "httpHost")) || normalizeOptionalString(inherited(entry, parent, "cliPath")) || typeof inherited(entry, parent, "httpPort") === "number" || typeof inherited(entry, parent, "autoStart") === "boolean");
}
function hasInvalidLegacyHttpUrl(entries, parent) {
return entries.some((entry) => {
const httpUrl = normalizeOptionalString(inherited(entry, parent, "httpUrl"));
if (!httpUrl) return false;
try {
normalizeSignalTransportUrl(httpUrl);
return false;
} catch {
return true;
}
});
}
function findInvalidLegacyDerivedEndpoint(entries, parent) {
for (const entry of entries) {
if (normalizeOptionalString(inherited(entry, parent, "httpUrl"))) continue;
const rawPort = inherited(entry, parent, "httpPort");
if (rawPort !== void 0 && !isValidSignalManagedNativePort(rawPort)) return "port";
const host = normalizeOptionalString(inherited(entry, parent, "httpHost")) ?? "127.0.0.1";
try {
buildSignalTransportHttpUrl(host, typeof rawPort === "number" ? rawPort : 8080);
} catch {
return "host";
}
}
}
function hasInvalidManagedTransportPort(transports) {
return transports.some((transport) => transport?.kind === "managed-native" && transport.httpPort !== void 0 && !isValidSignalManagedNativePort(transport.httpPort));
}
function requiresDetection(entry, parent, apiMode) {
if (apiMode !== void 0 && apiMode !== "auto") return false;
return Boolean(normalizeOptionalString(inherited(entry, parent, "httpUrl"))) || !resolveLegacyAutoStart(entry, parent);
}
function resolveLegacyAutoStart(entry, parent) {
const autoStart = inherited(entry, parent, "autoStart");
if (typeof autoStart === "boolean") return autoStart;
return !normalizeOptionalString(inherited(entry, parent, "httpUrl"));
}
function resolveManagedConnectionUrl(entry, parent) {
const httpUrl = normalizeOptionalString(inherited(entry, parent, "httpUrl"));
if (!httpUrl) return;
const normalizedUrl = normalizeSignalTransportUrl(httpUrl);
const endpoint = new URL(normalizedUrl);
const bindHost = (normalizeOptionalString(inherited(entry, parent, "httpHost")) ?? "127.0.0.1").replace(/^\[|\]$/g, "").toLowerCase();
const rawBindPort = inherited(entry, parent, "httpPort");
const bindPort = typeof rawBindPort === "number" ? rawBindPort : 8080;
const endpointHost = endpoint.hostname.replace(/^\[|\]$/g, "").toLowerCase();
const endpointPort = endpoint.port ? Number.parseInt(endpoint.port, 10) : endpoint.protocol === "https:" ? 443 : 80;
return endpoint.protocol === "http:" && endpointHost === bindHost && endpointPort === bindPort ? void 0 : normalizedUrl;
}
function buildManagedNativeTransport(entry, parent) {
const value = (key) => inherited(entry, parent, key);
const configPath = normalizeOptionalString(value("configPath"));
const cliPath = normalizeOptionalString(value("cliPath"));
const url = resolveManagedConnectionUrl(entry, parent);
const httpHost = normalizeOptionalString(value("httpHost"));
const httpPort = value("httpPort");
const startupTimeoutMs = value("startupTimeoutMs");
const receiveMode = value("receiveMode");
const ignoreStories = value("ignoreStories");
return {
kind: "managed-native",
...configPath ? { configPath } : {},
...cliPath ? { cliPath } : {},
...url ? { url } : {},
...httpHost ? { httpHost } : {},
...typeof httpPort === "number" ? { httpPort } : {},
...typeof startupTimeoutMs === "number" ? { startupTimeoutMs } : {},
...receiveMode === "on-start" || receiveMode === "manual" ? { receiveMode } : {},
...typeof ignoreStories === "boolean" ? { ignoreStories } : {}
};
}
function resolveLegacyTransportWithoutDetection(params) {
if (isSignalTransportConfig(params.entry.transport)) return params.entry.transport;
const baseUrl = legacyBaseUrl(params.entry, params.parent);
const autoStart = inherited(params.entry, params.parent, "autoStart");
if (params.apiMode === "container") return {
kind: "container",
url: baseUrl
};
if (params.apiMode === "native") return resolveLegacyAutoStart(params.entry, params.parent) ? buildManagedNativeTransport(params.entry, params.parent) : {
kind: "external-native",
url: baseUrl
};
if (requiresDetection(params.entry, params.parent, params.apiMode)) return;
if (autoStart === false) return {
kind: "external-native",
url: baseUrl
};
return buildManagedNativeTransport(params.entry, params.parent);
}
function clearLegacyTransportFields(entry) {
for (const field of LEGACY_TRANSPORT_FIELDS) delete entry[field];
}
function hasRootSignalAccount(entries) {
const root = entries[0];
return entries.length === 1 || Boolean(normalizeOptionalString(root?.account)) || isSignalTransportConfig(root?.transport);
}
function signalAccountIds(entries) {
const accounts = isRecord(entries[0]?.accounts) ? entries[0].accounts : {};
return Object.entries(accounts).filter(([, entry]) => isRecord(entry)).map(([accountId]) => accountId);
}
function isDefaultSignalAccountId(accountId) {
return Boolean(accountId?.trim()) && normalizeAccountId(accountId) === "default";
}
function resolveSignalAccountKey(accounts, accountId) {
const normalizedAccountId = normalizeAccountId(accountId);
return Object.keys(accounts).find((key) => Boolean(key.trim()) && normalizeAccountId(key) === normalizedAccountId);
}
function nestedDefaultOwnsEffectiveTransport(entries) {
const accounts = isRecord(entries[0]?.accounts) ? entries[0].accounts : {};
const nestedDefaultKey = resolveSignalAccountKey(accounts, DEFAULT_ACCOUNT_ID);
const nestedDefault = nestedDefaultKey ? accounts[nestedDefaultKey] : void 0;
return isRecord(nestedDefault) && (isSignalTransportConfig(nestedDefault.transport) || hasLegacyFields(nestedDefault));
}
function isDiscardedTransportEntry(entries, index) {
if (index === 0) return !hasRootSignalAccount(entries) || nestedDefaultOwnsEffectiveTransport(entries);
return isDefaultSignalAccountId(signalAccountIds(entries)[index - 1]) && isSignalTransportConfig(entries[0]?.transport);
}
function shouldMaterializeTransport(entries, index) {
if (isDiscardedTransportEntry(entries, index)) return false;
const entry = entries[index];
const parent = entries[0];
return Boolean(entry && parent && (isSignalTransportConfig(entry.transport) || wasLegacySignalAccountConfigured(entry, parent)));
}
function allocateMigratedManagedPorts(params) {
const reservedPorts = /* @__PURE__ */ new Set();
const rootIsAccount = hasRootSignalAccount(params.entries);
const nestedDefaultOffset = signalAccountIds(params.entries).findIndex((accountId) => isDefaultSignalAccountId(accountId));
const canonicalDefaultIndex = isSignalTransportConfig(params.entries[0]?.transport) ? 0 : nestedDefaultOffset >= 0 ? nestedDefaultOffset + 1 : rootIsAccount ? 0 : void 0;
for (const [index, transport] of params.transports.entries()) {
if (!transport || index === 0 && canonicalDefaultIndex !== 0) continue;
if (transport.kind !== "managed-native") {
const localPort = resolveLocalSignalTransportPort(transport.url);
if (localPort !== void 0) reservedPorts.add(localPort);
continue;
}
if (transport.url && !isSignalManagedNativeConnectionUrlForBind(transport)) {
const localConnectionPort = resolveLocalSignalTransportPort(transport.url);
if (localConnectionPort !== void 0) reservedPorts.add(localConnectionPort);
}
if (index === canonicalDefaultIndex || isRecord(params.entries[index]?.transport)) reservedPorts.add(transport.httpPort ?? 8080);
}
return params.transports.map((transport, index) => {
if (!transport || index === 0 && canonicalDefaultIndex !== 0) return transport;
if (transport.kind !== "managed-native") return transport;
if (isRecord(params.entries[index]?.transport) || index === canonicalDefaultIndex) return transport;
const rawPreferredPort = params.entries[index]?.httpPort;
const preferredPort = typeof rawPreferredPort === "number" ? rawPreferredPort : transport.httpPort;
const httpPort = allocateSignalManagedNativePort({
reservedPorts,
...typeof preferredPort === "number" ? { preferredPort } : {}
});
reservedPorts.add(httpPort);
return assignSignalManagedNativePort(transport, httpPort);
});
}
function applyMigratedSignalTransports(params) {
const next = structuredClone(params.cfg);
const nextSignal = next.channels?.signal;
if (!isRecord(nextSignal)) return;
const accountIds = signalAccountIds(params.entries);
const nextAccounts = isRecord(nextSignal.accounts) ? nextSignal.accounts : {};
const nextEntries = [nextSignal, ...Object.values(nextAccounts).filter(isRecord)];
const rootIsAccount = hasRootSignalAccount(params.entries);
const canonicalRootTransport = isSignalTransportConfig(params.entries[0]?.transport) ? params.entries[0].transport : void 0;
for (const [index, entry] of nextEntries.entries()) {
if (isDefaultSignalAccountId(index === 0 ? void 0 : accountIds[index - 1])) {
const defaultTransport = canonicalRootTransport ?? params.transports[index];
if (defaultTransport) nextSignal.transport = defaultTransport;
else delete nextSignal.transport;
delete entry.transport;
} else if (index === 0 && !rootIsAccount) delete entry.transport;
else if (params.transports[index]) entry.transport = params.transports[index];
else delete entry.transport;
clearLegacyTransportFields(entry);
}
delete nextSignal.apiMode;
return next;
}
function hasContainerTransportWithoutEffectiveAccount(cfg) {
const signal = cfg.channels?.signal;
if (!isRecord(signal)) return false;
const accounts = isRecord(signal.accounts) ? signal.accounts : {};
const rootTransport = isSignalTransportConfig(signal.transport) ? signal.transport : void 0;
const defaultKey = resolveSignalAccountKey(accounts, DEFAULT_ACCOUNT_ID);
const defaultEntry = defaultKey ? accounts[defaultKey] : void 0;
const defaultAccount = isRecord(defaultEntry) && defaultEntry.account !== void 0 ? normalizeOptionalString(defaultEntry.account) : normalizeOptionalString(signal.account);
const channelEnabled = signal.enabled !== false;
const defaultEnabled = !isRecord(defaultEntry) || defaultEntry.enabled !== false;
if (rootTransport?.kind === "container" && channelEnabled && defaultEnabled && !defaultAccount) return true;
for (const [accountId, entry] of Object.entries(accounts)) {
if (!isRecord(entry)) continue;
if (!channelEnabled || entry.enabled === false) continue;
const isDefaultAccount = isDefaultSignalAccountId(accountId);
if ((isDefaultAccount && rootTransport ? rootTransport : isSignalTransportConfig(entry.transport) ? entry.transport : void 0)?.kind !== "container" || isDefaultAccount && rootTransport) continue;
if (!(entry.account === void 0 ? normalizeOptionalString(signal.account) : normalizeOptionalString(entry.account))) return true;
}
return false;
}
function migrateLegacySignalTransportConfigSync(cfg) {
const signal = cfg.channels?.signal;
if (!isRecord(signal)) return {
config: cfg,
changes: []
};
const accounts = isRecord(signal.accounts) ? signal.accounts : {};
if (!(Object.hasOwn(signal, "apiMode") || hasLegacyFields(signal) || Object.values(accounts).some((entry) => isRecord(entry) && hasLegacyFields(entry)))) return {
config: cfg,
changes: []
};
const entries = [signal, ...Object.values(accounts).filter(isRecord)];
const legacyResolutionEntries = entries.filter((_, index) => shouldMaterializeTransport(entries, index)).filter((entry) => !isSignalTransportConfig(entry.transport));
const invalidDerivedEndpoint = findInvalidLegacyDerivedEndpoint(legacyResolutionEntries, signal);
if (invalidDerivedEndpoint) return {
config: cfg,
changes: [],
warnings: [invalidDerivedEndpoint === "port" ? PENDING_LEGACY_INVALID_PORT_WARNING : PENDING_LEGACY_INVALID_HOST_WARNING]
};
if (hasInvalidLegacyHttpUrl(legacyResolutionEntries, signal)) return {
config: cfg,
changes: [],
warnings: [PENDING_LEGACY_INVALID_URL_WARNING]
};
const resolvedTransports = entries.map((entry, index) => {
if (!shouldMaterializeTransport(entries, index)) return;
return resolveLegacyTransportWithoutDetection({
entry,
parent: signal,
apiMode: signal.apiMode
});
});
if (hasInvalidManagedTransportPort(resolvedTransports)) return {
config: cfg,
changes: [],
warnings: [PENDING_LEGACY_INVALID_PORT_WARNING]
};
const transports = allocateMigratedManagedPorts({
entries,
transports: resolvedTransports
});
if (transports.some((transport, index) => shouldMaterializeTransport(entries, index) && !transport)) return {
config: cfg,
changes: [],
warnings: [PENDING_LEGACY_TRANSPORT_WARNING]
};
const next = applyMigratedSignalTransports({
cfg,
entries,
transports
});
if (!next) return {
config: cfg,
changes: []
};
if (hasContainerTransportWithoutEffectiveAccount(next)) return {
config: cfg,
changes: [],
warnings: [PENDING_LEGACY_CONTAINER_ACCOUNT_WARNING]
};
return {
config: next,
changes: ["Migrated channels.signal transport settings to concrete account-owned transport objects."]
};
}
//#endregion
//#region extensions/signal/config-doctor-api.ts
const RETIRED_SIGNAL_ACCOUNT_TRANSPORT_FIELDS = [
"configPath",
"httpUrl",
"httpHost",
"httpPort",
"cliPath",
"autoStart",
"startupTimeoutMs",
"receiveMode",
"ignoreStories"
];
function hasRetiredSignalAccountTransportFields(value) {
return isRecord(value) && RETIRED_SIGNAL_ACCOUNT_TRANSPORT_FIELDS.some((field) => Object.hasOwn(value, field));
}
function hasRetiredSignalAccountMapTransportFields(value) {
return isRecord(value) && Object.values(value).some(hasRetiredSignalAccountTransportFields);
}
const streamingAliasMigration = defineChannelAliasMigration({
channelId: "signal",
streaming: {
defaultMode: "partial",
deliveryOnly: true
},
accountStreamingReplacesRoot: true
});
const legacyConfigRules = [
...streamingAliasMigration.legacyConfigRules,
{
path: ["channels", "signal"],
message: "Signal transport config is now account-owned; run \"openclaw doctor --fix\" to migrate retired channels.signal transport fields.",
match: (value) => isRecord(value) && (Object.hasOwn(value, "apiMode") || hasRetiredSignalAccountTransportFields(value))
},
{
path: [
"channels",
"signal",
"accounts"
],
message: "Signal transport config is now account-owned; run \"openclaw doctor --fix\" to migrate retired per-account transport fields.",
match: hasRetiredSignalAccountMapTransportFields
}
];
function normalizeCompatibilityConfig({ cfg }) {
const streaming = streamingAliasMigration.normalizeChannelConfig({ cfg });
const transport = migrateLegacySignalTransportConfigSync(streaming.config);
return {
config: transport.config,
changes: [...streaming.changes, ...transport.changes],
...transport.warnings?.length ? { warnings: transport.warnings } : {}
};
}
//#endregion
export { legacyConfigRules, normalizeCompatibilityConfig };