mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 04:51:25 +00:00
refactor(config): unify streaming config across channels
This commit is contained in:
@@ -68,6 +68,42 @@ describe("doctor config flow", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves discord streaming intent while stripping unsupported keys on repair", async () => {
|
||||
const result = await runDoctorConfigWithInput({
|
||||
repair: true,
|
||||
config: {
|
||||
channels: {
|
||||
discord: {
|
||||
streaming: true,
|
||||
lifecycle: {
|
||||
enabled: true,
|
||||
reactions: {
|
||||
queued: "⏳",
|
||||
thinking: "🧠",
|
||||
tool: "🔧",
|
||||
done: "✅",
|
||||
error: "❌",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const cfg = result.cfg as {
|
||||
channels: {
|
||||
discord: {
|
||||
streamMode?: string;
|
||||
streaming?: string;
|
||||
lifecycle?: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
expect(cfg.channels.discord.streaming).toBe("partial");
|
||||
expect(cfg.channels.discord.streamMode).toBeUndefined();
|
||||
expect(cfg.channels.discord.lifecycle).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves Telegram @username allowFrom entries to numeric IDs on repair", async () => {
|
||||
const fetchSpy = vi.fn(async (url: string) => {
|
||||
const u = String(url);
|
||||
|
||||
@@ -145,4 +145,81 @@ describe("normalizeLegacyConfigValues", () => {
|
||||
"Moved channels.discord.accounts.work.dm.allowFrom → channels.discord.accounts.work.allowFrom.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Discord streaming boolean alias to streaming enum", () => {
|
||||
const res = normalizeLegacyConfigValues({
|
||||
channels: {
|
||||
discord: {
|
||||
streaming: true,
|
||||
accounts: {
|
||||
work: {
|
||||
streaming: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.discord?.streaming).toBe("partial");
|
||||
expect(res.config.channels?.discord?.streamMode).toBeUndefined();
|
||||
expect(res.config.channels?.discord?.accounts?.work?.streaming).toBe("off");
|
||||
expect(res.config.channels?.discord?.accounts?.work?.streamMode).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Normalized channels.discord.streaming boolean → enum (partial).",
|
||||
"Normalized channels.discord.accounts.work.streaming boolean → enum (off).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Discord legacy streamMode into streaming enum", () => {
|
||||
const res = normalizeLegacyConfigValues({
|
||||
channels: {
|
||||
discord: {
|
||||
streaming: false,
|
||||
streamMode: "block",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.discord?.streaming).toBe("block");
|
||||
expect(res.config.channels?.discord?.streamMode).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.discord.streamMode → channels.discord.streaming (block).",
|
||||
"Normalized channels.discord.streaming boolean → enum (block).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Telegram streamMode into streaming enum", () => {
|
||||
const res = normalizeLegacyConfigValues({
|
||||
channels: {
|
||||
telegram: {
|
||||
streamMode: "block",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.telegram?.streaming).toBe("block");
|
||||
expect(res.config.channels?.telegram?.streamMode).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.telegram.streamMode → channels.telegram.streaming (block).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Slack legacy streaming keys to unified config", () => {
|
||||
const res = normalizeLegacyConfigValues({
|
||||
channels: {
|
||||
slack: {
|
||||
streaming: false,
|
||||
streamMode: "status_final",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.slack?.streaming).toBe("progress");
|
||||
expect(res.config.channels?.slack?.nativeStreaming).toBe(false);
|
||||
expect(res.config.channels?.slack?.streamMode).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.slack.streamMode → channels.slack.streaming (progress).",
|
||||
"Moved channels.slack.streaming (boolean) → channels.slack.nativeStreaming (false).",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
resolveDiscordPreviewStreamMode,
|
||||
resolveSlackNativeStreaming,
|
||||
resolveSlackStreamingMode,
|
||||
resolveTelegramPreviewStreamMode,
|
||||
} from "../config/discord-preview-streaming.js";
|
||||
|
||||
export function normalizeLegacyConfigValues(cfg: OpenClawConfig): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
@@ -90,20 +97,178 @@ export function normalizeLegacyConfigValues(cfg: OpenClawConfig): {
|
||||
return { entry: updated, changed };
|
||||
};
|
||||
|
||||
const normalizeProvider = (provider: "slack" | "discord") => {
|
||||
const normalizeTelegramStreamingAliases = (params: {
|
||||
entry: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
}): { entry: Record<string, unknown>; changed: boolean } => {
|
||||
let updated = params.entry;
|
||||
const hadLegacyStreamMode = updated.streamMode !== undefined;
|
||||
const beforeStreaming = updated.streaming;
|
||||
const resolved = resolveTelegramPreviewStreamMode(updated);
|
||||
const shouldNormalize =
|
||||
hadLegacyStreamMode ||
|
||||
typeof beforeStreaming === "boolean" ||
|
||||
(typeof beforeStreaming === "string" && beforeStreaming !== resolved);
|
||||
if (!shouldNormalize) {
|
||||
return { entry: updated, changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
if (beforeStreaming !== resolved) {
|
||||
updated = { ...updated, streaming: resolved };
|
||||
changed = true;
|
||||
}
|
||||
if (hadLegacyStreamMode) {
|
||||
const { streamMode: _ignored, ...rest } = updated;
|
||||
updated = rest;
|
||||
changed = true;
|
||||
changes.push(
|
||||
`Moved ${params.pathPrefix}.streamMode → ${params.pathPrefix}.streaming (${resolved}).`,
|
||||
);
|
||||
}
|
||||
if (typeof beforeStreaming === "boolean") {
|
||||
changes.push(`Normalized ${params.pathPrefix}.streaming boolean → enum (${resolved}).`);
|
||||
} else if (typeof beforeStreaming === "string" && beforeStreaming !== resolved) {
|
||||
changes.push(
|
||||
`Normalized ${params.pathPrefix}.streaming (${beforeStreaming}) → (${resolved}).`,
|
||||
);
|
||||
}
|
||||
|
||||
return { entry: updated, changed };
|
||||
};
|
||||
|
||||
const normalizeDiscordStreamingAliases = (params: {
|
||||
entry: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
}): { entry: Record<string, unknown>; changed: boolean } => {
|
||||
let updated = params.entry;
|
||||
const hadLegacyStreamMode = updated.streamMode !== undefined;
|
||||
const beforeStreaming = updated.streaming;
|
||||
const resolved = resolveDiscordPreviewStreamMode(updated);
|
||||
const shouldNormalize =
|
||||
hadLegacyStreamMode ||
|
||||
typeof beforeStreaming === "boolean" ||
|
||||
(typeof beforeStreaming === "string" && beforeStreaming !== resolved);
|
||||
if (!shouldNormalize) {
|
||||
return { entry: updated, changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
if (beforeStreaming !== resolved) {
|
||||
updated = { ...updated, streaming: resolved };
|
||||
changed = true;
|
||||
}
|
||||
if (hadLegacyStreamMode) {
|
||||
const { streamMode: _ignored, ...rest } = updated;
|
||||
updated = rest;
|
||||
changed = true;
|
||||
changes.push(
|
||||
`Moved ${params.pathPrefix}.streamMode → ${params.pathPrefix}.streaming (${resolved}).`,
|
||||
);
|
||||
}
|
||||
if (typeof beforeStreaming === "boolean") {
|
||||
changes.push(`Normalized ${params.pathPrefix}.streaming boolean → enum (${resolved}).`);
|
||||
} else if (typeof beforeStreaming === "string" && beforeStreaming !== resolved) {
|
||||
changes.push(
|
||||
`Normalized ${params.pathPrefix}.streaming (${beforeStreaming}) → (${resolved}).`,
|
||||
);
|
||||
}
|
||||
|
||||
return { entry: updated, changed };
|
||||
};
|
||||
|
||||
const normalizeSlackStreamingAliases = (params: {
|
||||
entry: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
}): { entry: Record<string, unknown>; changed: boolean } => {
|
||||
let updated = params.entry;
|
||||
const hadLegacyStreamMode = updated.streamMode !== undefined;
|
||||
const legacyStreaming = updated.streaming;
|
||||
const beforeStreaming = updated.streaming;
|
||||
const beforeNativeStreaming = updated.nativeStreaming;
|
||||
const resolvedStreaming = resolveSlackStreamingMode(updated);
|
||||
const resolvedNativeStreaming = resolveSlackNativeStreaming(updated);
|
||||
const shouldNormalize =
|
||||
hadLegacyStreamMode ||
|
||||
typeof legacyStreaming === "boolean" ||
|
||||
(typeof legacyStreaming === "string" && legacyStreaming !== resolvedStreaming);
|
||||
if (!shouldNormalize) {
|
||||
return { entry: updated, changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
if (beforeStreaming !== resolvedStreaming) {
|
||||
updated = { ...updated, streaming: resolvedStreaming };
|
||||
changed = true;
|
||||
}
|
||||
if (
|
||||
typeof beforeNativeStreaming !== "boolean" ||
|
||||
beforeNativeStreaming !== resolvedNativeStreaming
|
||||
) {
|
||||
updated = { ...updated, nativeStreaming: resolvedNativeStreaming };
|
||||
changed = true;
|
||||
}
|
||||
if (hadLegacyStreamMode) {
|
||||
const { streamMode: _ignored, ...rest } = updated;
|
||||
updated = rest;
|
||||
changed = true;
|
||||
changes.push(
|
||||
`Moved ${params.pathPrefix}.streamMode → ${params.pathPrefix}.streaming (${resolvedStreaming}).`,
|
||||
);
|
||||
}
|
||||
if (typeof legacyStreaming === "boolean") {
|
||||
changes.push(
|
||||
`Moved ${params.pathPrefix}.streaming (boolean) → ${params.pathPrefix}.nativeStreaming (${resolvedNativeStreaming}).`,
|
||||
);
|
||||
} else if (typeof legacyStreaming === "string" && legacyStreaming !== resolvedStreaming) {
|
||||
changes.push(
|
||||
`Normalized ${params.pathPrefix}.streaming (${legacyStreaming}) → (${resolvedStreaming}).`,
|
||||
);
|
||||
}
|
||||
|
||||
return { entry: updated, changed };
|
||||
};
|
||||
|
||||
const normalizeProvider = (provider: "telegram" | "slack" | "discord") => {
|
||||
const channels = next.channels as Record<string, unknown> | undefined;
|
||||
const rawEntry = channels?.[provider];
|
||||
if (!isRecord(rawEntry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const base = normalizeDmAliases({
|
||||
provider,
|
||||
entry: rawEntry,
|
||||
pathPrefix: `channels.${provider}`,
|
||||
});
|
||||
let updated = base.entry;
|
||||
let changed = base.changed;
|
||||
let updated = rawEntry;
|
||||
let changed = false;
|
||||
if (provider !== "telegram") {
|
||||
const base = normalizeDmAliases({
|
||||
provider,
|
||||
entry: rawEntry,
|
||||
pathPrefix: `channels.${provider}`,
|
||||
});
|
||||
updated = base.entry;
|
||||
changed = base.changed;
|
||||
}
|
||||
if (provider === "telegram") {
|
||||
const streaming = normalizeTelegramStreamingAliases({
|
||||
entry: updated,
|
||||
pathPrefix: `channels.${provider}`,
|
||||
});
|
||||
updated = streaming.entry;
|
||||
changed = changed || streaming.changed;
|
||||
} else if (provider === "discord") {
|
||||
const streaming = normalizeDiscordStreamingAliases({
|
||||
entry: updated,
|
||||
pathPrefix: `channels.${provider}`,
|
||||
});
|
||||
updated = streaming.entry;
|
||||
changed = changed || streaming.changed;
|
||||
} else if (provider === "slack") {
|
||||
const streaming = normalizeSlackStreamingAliases({
|
||||
entry: updated,
|
||||
pathPrefix: `channels.${provider}`,
|
||||
});
|
||||
updated = streaming.entry;
|
||||
changed = changed || streaming.changed;
|
||||
}
|
||||
|
||||
const rawAccounts = updated.accounts;
|
||||
if (isRecord(rawAccounts)) {
|
||||
@@ -113,13 +278,41 @@ export function normalizeLegacyConfigValues(cfg: OpenClawConfig): {
|
||||
if (!isRecord(rawAccount)) {
|
||||
continue;
|
||||
}
|
||||
const res = normalizeDmAliases({
|
||||
provider,
|
||||
entry: rawAccount,
|
||||
pathPrefix: `channels.${provider}.accounts.${accountId}`,
|
||||
});
|
||||
if (res.changed) {
|
||||
accounts[accountId] = res.entry;
|
||||
let accountEntry = rawAccount;
|
||||
let accountChanged = false;
|
||||
if (provider !== "telegram") {
|
||||
const res = normalizeDmAliases({
|
||||
provider,
|
||||
entry: rawAccount,
|
||||
pathPrefix: `channels.${provider}.accounts.${accountId}`,
|
||||
});
|
||||
accountEntry = res.entry;
|
||||
accountChanged = res.changed;
|
||||
}
|
||||
if (provider === "telegram") {
|
||||
const streaming = normalizeTelegramStreamingAliases({
|
||||
entry: accountEntry,
|
||||
pathPrefix: `channels.${provider}.accounts.${accountId}`,
|
||||
});
|
||||
accountEntry = streaming.entry;
|
||||
accountChanged = accountChanged || streaming.changed;
|
||||
} else if (provider === "discord") {
|
||||
const streaming = normalizeDiscordStreamingAliases({
|
||||
entry: accountEntry,
|
||||
pathPrefix: `channels.${provider}.accounts.${accountId}`,
|
||||
});
|
||||
accountEntry = streaming.entry;
|
||||
accountChanged = accountChanged || streaming.changed;
|
||||
} else if (provider === "slack") {
|
||||
const streaming = normalizeSlackStreamingAliases({
|
||||
entry: accountEntry,
|
||||
pathPrefix: `channels.${provider}.accounts.${accountId}`,
|
||||
});
|
||||
accountEntry = streaming.entry;
|
||||
accountChanged = accountChanged || streaming.changed;
|
||||
}
|
||||
if (accountChanged) {
|
||||
accounts[accountId] = accountEntry;
|
||||
accountsChanged = true;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +333,7 @@ export function normalizeLegacyConfigValues(cfg: OpenClawConfig): {
|
||||
}
|
||||
};
|
||||
|
||||
normalizeProvider("telegram");
|
||||
normalizeProvider("slack");
|
||||
normalizeProvider("discord");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user