openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
194 lines (193 loc) • 7.95 kB
JavaScript
import { S as normalizeChannelConfigEntries, T as normalizeLowercaseStringOrEmpty, a as defineChannelAliasMigration, b as hasLegacyAccountStreamingAliases, k as asNullableRecord, n as defineKeyMoveMigration, o as stripRetiredChannelKeys } from "./runtime-doctor-migrations-BhouWAUC.js";
import "./string-coerce-runtime-CM4JCyJk.js";
//#region src/utils/boolean.ts
/** Returns only real boolean values and leaves boolean-like strings for explicit parsing. */
function asBoolean(value) {
return typeof value === "boolean" ? value : void 0;
}
//#endregion
//#region src/channels/streaming-config-readers.ts
function getChannelStreamingConfigObject(entry) {
const streaming = asNullableRecord(entry?.streaming);
return streaming ? streaming : void 0;
}
function resolveChannelStreamingNativeTransport(entry) {
return asBoolean(getChannelStreamingConfigObject(entry)?.nativeTransport);
}
//#endregion
//#region extensions/slack/src/streaming-compat.ts
function resolveSlackStreamingMode(params = {}) {
const mode = normalizeLowercaseStringOrEmpty(getChannelStreamingConfigObject(params)?.mode ?? params.streaming);
if (mode === "off" || mode === "partial" || mode === "block" || mode === "progress") return mode;
switch (normalizeLowercaseStringOrEmpty(params.streamMode)) {
case "append": return "block";
case "status_final": return "progress";
case "replace": return "partial";
default: return typeof params.streaming === "boolean" ? params.streaming ? "partial" : "off" : "progress";
}
}
function resolveSlackNativeStreaming(params = {}) {
return resolveChannelStreamingNativeTransport(params) ?? (typeof params.nativeStreaming === "boolean" ? params.nativeStreaming : typeof params.streaming === "boolean" ? params.streaming : true);
}
//#endregion
//#region extensions/slack/src/doctor-contract.ts
const streamingAliasMigration = defineChannelAliasMigration({
channelId: "slack",
streaming: {
defaultMode: "partial",
resolveMode: resolveSlackStreamingMode,
resolveNativeTransport: resolveSlackNativeStreaming
},
dm: {
root: true,
accounts: true
}
});
const dmReplyModeMigration = defineKeyMoveMigration({
from: ["dm", "replyToMode"],
to: ["replyToModeByChatType", "direct"]
});
const threadMentionPolicyMigration = defineKeyMoveMigration({
from: ["thread", "requireExplicitMention"],
to: ["implicitMentions", "threadParticipation"],
map: (value) => typeof value === "boolean" ? { value: !value } : null,
pruneEmptySource: true,
movedMessage: ({ sourcePath, targetPath, mappedValue }) => `Moved ${sourcePath} → ${targetPath} (${String(mappedValue)}).`
});
function hasInteractiveRepliesCapability(value) {
const capabilities = asNullableRecord(value)?.capabilities;
if (Array.isArray(capabilities)) return capabilities.some((entry) => typeof entry === "string" && entry.trim().toLowerCase() === "interactivereplies");
const capabilitiesRecord = asNullableRecord(capabilities);
return Boolean(capabilitiesRecord && (Object.keys(capabilitiesRecord).length === 0 || Object.hasOwn(capabilitiesRecord, "interactiveReplies")));
}
function hasEnterpriseOrgInstall(value) {
return Object.hasOwn(asNullableRecord(value) ?? {}, "enterpriseOrgInstall");
}
function removeInteractiveRepliesCapability(params) {
const capabilities = params.entry.capabilities;
let nextCapabilities;
let removedEmptyObject = false;
if (Array.isArray(capabilities)) {
nextCapabilities = capabilities.filter((entry) => !(typeof entry === "string" && entry.trim().toLowerCase() === "interactivereplies"));
if (nextCapabilities.length === capabilities.length) return {
entry: params.entry,
changed: false
};
} else {
const capabilitiesRecord = asNullableRecord(capabilities);
if (!capabilitiesRecord || Object.keys(capabilitiesRecord).length > 0 && !Object.hasOwn(capabilitiesRecord, "interactiveReplies")) return {
entry: params.entry,
changed: false
};
removedEmptyObject = Object.keys(capabilitiesRecord).length === 0;
const { interactiveReplies: _retired, ...rest } = capabilitiesRecord;
nextCapabilities = rest;
}
const entry = { ...params.entry };
if (Array.isArray(nextCapabilities) ? nextCapabilities.length === 0 : Object.keys(nextCapabilities).length === 0) delete entry.capabilities;
else entry.capabilities = nextCapabilities;
params.changes.push(removedEmptyObject ? `Removed retired empty ${params.pathPrefix}.capabilities object; use typed presentation actions instead.` : `Removed retired ${params.pathPrefix}.capabilities.interactiveReplies; use typed presentation actions instead.`);
return {
entry,
changed: true
};
}
const legacyConfigRules = [
...streamingAliasMigration.legacyConfigRules,
{
path: [
"channels",
"slack",
"enterpriseOrgInstall"
],
message: "channels.slack.enterpriseOrgInstall is retired; Slack now detects org-wide installations automatically. Run \"openclaw doctor --fix\"."
},
{
path: [
"channels",
"slack",
"accounts"
],
message: "channels.slack.accounts.<id>.enterpriseOrgInstall is retired; Slack now detects org-wide installations automatically. Run \"openclaw doctor --fix\".",
match: (value) => hasLegacyAccountStreamingAliases(value, hasEnterpriseOrgInstall)
},
{
path: ["channels", "slack"],
message: "channels.slack.capabilities.interactiveReplies is retired; use typed presentation actions instead. Run \"openclaw doctor --fix\".",
match: hasInteractiveRepliesCapability
},
{
path: [
"channels",
"slack",
"accounts"
],
message: "channels.slack.accounts.<id>.capabilities.interactiveReplies is retired; use typed presentation actions instead. Run \"openclaw doctor --fix\".",
match: (value) => hasLegacyAccountStreamingAliases(value, hasInteractiveRepliesCapability)
},
{
path: ["channels", "slack"],
message: "channels.slack.dm.replyToMode moved to replyToModeByChatType.direct. Run \"openclaw doctor --fix\".",
match: dmReplyModeMigration.hasLegacy
},
{
path: [
"channels",
"slack",
"accounts"
],
message: "channels.slack.accounts.<id>.dm.replyToMode moved to replyToModeByChatType.direct. Run \"openclaw doctor --fix\".",
match: (value) => hasLegacyAccountStreamingAliases(value, dmReplyModeMigration.hasLegacy)
},
{
path: ["channels", "slack"],
message: "channels.slack.thread.requireExplicitMention is legacy; use channels.slack.implicitMentions.threadParticipation instead. Run \"openclaw doctor --fix\".",
match: threadMentionPolicyMigration.hasLegacy
},
{
path: [
"channels",
"slack",
"accounts"
],
message: "channels.slack.accounts.<id>.thread.requireExplicitMention is legacy; use channels.slack.accounts.<id>.implicitMentions.threadParticipation instead. Run \"openclaw doctor --fix\".",
match: (value) => hasLegacyAccountStreamingAliases(value, threadMentionPolicyMigration.hasLegacy)
}
];
function normalizeSlackEntry(params) {
const retiredInteractiveReplies = removeInteractiveRepliesCapability(params);
const dm = dmReplyModeMigration.normalize({
...params,
entry: retiredInteractiveReplies.entry
});
const thread = threadMentionPolicyMigration.normalize({
...params,
entry: dm.entry
});
return {
entry: thread.entry,
changed: retiredInteractiveReplies.changed || dm.changed || thread.changed
};
}
function normalizeCompatibilityConfig({ cfg }) {
const changes = [];
const retired = stripRetiredChannelKeys({
cfg,
channelId: "slack",
keys: /* @__PURE__ */ new Set(["enterpriseOrgInstall"]),
scope: "root-and-accounts",
onRemove: ({ key, pathPrefix }) => changes.push(`Removed retired ${pathPrefix}.${key}; Slack detects org-wide installations automatically.`)
});
const aliases = streamingAliasMigration.normalizeChannelConfig({
cfg: retired.config,
changes
});
return normalizeChannelConfigEntries({
cfg: aliases.config,
channelId: "slack",
changes,
normalizeEntry: normalizeSlackEntry
});
}
//#endregion
export { legacyConfigRules, normalizeCompatibilityConfig };