mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 22:50:22 +00:00
fix(config): normalize channel streaming config shape (#61381)
* feat(config): add canonical streaming config helpers * refactor(runtime): prefer canonical streaming accessors * feat(config): normalize preview channel streaming shape * test(config): lock streaming normalization followups * fix(config): polish streaming migration edges * chore(config): refresh streaming baseline hash
This commit is contained in:
@@ -425,3 +425,672 @@ describe("config paths", () => {
|
||||
expect(getConfigValueAtPath(root, parsed.path)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("config strict validation", () => {
|
||||
it("rejects unknown fields", async () => {
|
||||
const res = validateConfigObject({
|
||||
agents: { list: [{ id: "pi" }] },
|
||||
customUnknownField: { nested: "value" },
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts documented agents.list[].params overrides", () => {
|
||||
const res = validateConfigObject({
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
params: {
|
||||
cacheRetention: "none",
|
||||
temperature: 0.4,
|
||||
maxTokens: 8192,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
if (res.ok) {
|
||||
expect(res.config.agents?.list?.[0]?.params).toEqual({
|
||||
cacheRetention: "none",
|
||||
temperature: 0.4,
|
||||
maxTokens: 8192,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts top-level memorySearch via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
memorySearch: {
|
||||
provider: "local",
|
||||
fallback: "none",
|
||||
query: { maxResults: 7 },
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "memorySearch")).toBe(true);
|
||||
expect(snap.sourceConfig.agents?.defaults?.memorySearch).toMatchObject({
|
||||
provider: "local",
|
||||
fallback: "none",
|
||||
query: { maxResults: 7 },
|
||||
});
|
||||
expect((snap.sourceConfig as { memorySearch?: unknown }).memorySearch).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts top-level heartbeat agent settings via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
heartbeat: {
|
||||
every: "30m",
|
||||
model: "anthropic/claude-3-5-haiku-20241022",
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
|
||||
expect(snap.sourceConfig.agents?.defaults?.heartbeat).toMatchObject({
|
||||
every: "30m",
|
||||
model: "anthropic/claude-3-5-haiku-20241022",
|
||||
});
|
||||
expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts top-level heartbeat visibility via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
heartbeat: {
|
||||
showOk: true,
|
||||
showAlerts: false,
|
||||
useIndicator: true,
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "heartbeat")).toBe(true);
|
||||
expect(snap.sourceConfig.channels?.defaults?.heartbeat).toMatchObject({
|
||||
showOk: true,
|
||||
showAlerts: false,
|
||||
useIndicator: true,
|
||||
});
|
||||
expect((snap.sourceConfig as { heartbeat?: unknown }).heartbeat).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy messages.tts provider keys via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
messages: {
|
||||
tts: {
|
||||
provider: "elevenlabs",
|
||||
elevenlabs: {
|
||||
apiKey: "test-key",
|
||||
voiceId: "voice-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "messages.tts")).toBe(true);
|
||||
expect(snap.sourceConfig.messages?.tts?.providers?.elevenlabs).toEqual({
|
||||
apiKey: "test-key",
|
||||
voiceId: "voice-1",
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.messages?.tts as Record<string, unknown> | undefined)?.elevenlabs,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy talk flat fields via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
talk: {
|
||||
voiceId: "voice-1",
|
||||
modelId: "eleven_v3",
|
||||
apiKey: "test-key",
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "talk")).toBe(true);
|
||||
expect(snap.sourceConfig.talk?.providers?.elevenlabs).toEqual({
|
||||
voiceId: "voice-1",
|
||||
modelId: "eleven_v3",
|
||||
apiKey: "test-key",
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.talk as Record<string, unknown> | undefined)?.voiceId,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(snap.sourceConfig.talk as Record<string, unknown> | undefined)?.modelId,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(snap.sourceConfig.talk as Record<string, unknown> | undefined)?.apiKey,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy sandbox perSession via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
perSession: true,
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "pi",
|
||||
sandbox: {
|
||||
perSession: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "agents.defaults.sandbox")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "agents.list")).toBe(true);
|
||||
expect(snap.sourceConfig.agents?.defaults?.sandbox).toEqual({
|
||||
scope: "session",
|
||||
});
|
||||
expect(snap.sourceConfig.agents?.list?.[0]?.sandbox).toEqual({
|
||||
scope: "shared",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy x_search auth via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
tools: {
|
||||
web: {
|
||||
x_search: {
|
||||
apiKey: "test-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "tools.web.x_search.apiKey")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.sourceConfig.plugins?.entries?.xai?.config?.webSearch).toMatchObject({
|
||||
apiKey: "test-key",
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.tools?.web?.x_search as Record<string, unknown> | undefined)?.apiKey,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy thread binding ttlHours via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
session: {
|
||||
threadBindings: {
|
||||
ttlHours: 24,
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
discord: {
|
||||
threadBindings: {
|
||||
ttlHours: 12,
|
||||
},
|
||||
accounts: {
|
||||
alpha: {
|
||||
threadBindings: {
|
||||
ttlHours: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "session.threadBindings")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels")).toBe(true);
|
||||
expect(snap.sourceConfig.session?.threadBindings).toMatchObject({
|
||||
idleHours: 24,
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.threadBindings).toMatchObject({
|
||||
idleHours: 12,
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.accounts?.alpha?.threadBindings).toMatchObject({
|
||||
idleHours: 6,
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.session?.threadBindings as Record<string, unknown> | undefined)
|
||||
?.ttlHours,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.discord?.threadBindings as Record<string, unknown> | undefined)
|
||||
?.ttlHours,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(
|
||||
snap.sourceConfig.channels?.discord?.accounts?.alpha?.threadBindings as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
)?.ttlHours,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy channel streaming aliases via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
telegram: {
|
||||
streamMode: "block",
|
||||
chunkMode: "newline",
|
||||
blockStreaming: true,
|
||||
draftChunk: {
|
||||
minChars: 120,
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
streaming: false,
|
||||
blockStreamingCoalesce: {
|
||||
idleMs: 250,
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
streamMode: "block",
|
||||
draftChunk: {
|
||||
maxChars: 900,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
googlechat: {
|
||||
streamMode: "append",
|
||||
accounts: {
|
||||
work: {
|
||||
streamMode: "replace",
|
||||
},
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
streaming: true,
|
||||
nativeStreaming: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.telegram")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true);
|
||||
expect(snap.sourceConfig.channels?.telegram).toMatchObject({
|
||||
streaming: {
|
||||
mode: "block",
|
||||
chunkMode: "newline",
|
||||
block: {
|
||||
enabled: true,
|
||||
},
|
||||
preview: {
|
||||
chunk: {
|
||||
minChars: 120,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.telegram as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(snap.sourceConfig.channels?.discord).toMatchObject({
|
||||
streaming: {
|
||||
mode: "off",
|
||||
block: {
|
||||
coalesce: {
|
||||
idleMs: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.accounts?.work).toMatchObject({
|
||||
streaming: {
|
||||
mode: "block",
|
||||
preview: {
|
||||
chunk: {
|
||||
maxChars: 900,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.googlechat as Record<string, unknown> | undefined)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(
|
||||
snap.sourceConfig.channels?.googlechat?.accounts?.work as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
)?.streamMode,
|
||||
).toBeUndefined();
|
||||
expect(snap.sourceConfig.channels?.slack).toMatchObject({
|
||||
streaming: {
|
||||
mode: "partial",
|
||||
nativeTransport: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy nested channel allow aliases via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
slack: {
|
||||
channels: {
|
||||
ops: {
|
||||
allow: false,
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
channels: {
|
||||
general: {
|
||||
allow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
googlechat: {
|
||||
groups: {
|
||||
"spaces/aaa": {
|
||||
allow: false,
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
groups: {
|
||||
"spaces/bbb": {
|
||||
allow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
guilds: {
|
||||
"100": {
|
||||
channels: {
|
||||
general: {
|
||||
allow: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
guilds: {
|
||||
"200": {
|
||||
channels: {
|
||||
help: {
|
||||
allow: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.slack.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.googlechat.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord")).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.sourceConfig.channels?.slack?.channels?.ops).toMatchObject({
|
||||
enabled: false,
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.googlechat?.groups?.["spaces/aaa"]).toMatchObject({
|
||||
enabled: false,
|
||||
});
|
||||
expect(snap.sourceConfig.channels?.discord?.guilds?.["100"]?.channels?.general).toMatchObject(
|
||||
{
|
||||
enabled: false,
|
||||
},
|
||||
);
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.slack?.channels?.ops as Record<string, unknown> | undefined)
|
||||
?.allow,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(
|
||||
snap.sourceConfig.channels?.googlechat?.groups?.["spaces/aaa"] as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
)?.allow,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(
|
||||
snap.sourceConfig.channels?.discord?.guilds?.["100"]?.channels?.general as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
)?.allow,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts telegram groupMentionsOnly via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
telegram: {
|
||||
groupMentionsOnly: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(
|
||||
snap.legacyIssues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly"),
|
||||
).toBe(true);
|
||||
expect(snap.sourceConfig.channels?.telegram?.groups?.["*"]).toMatchObject({
|
||||
requireMention: true,
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.telegram as Record<string, unknown> | undefined)
|
||||
?.groupMentionsOnly,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy plugins.entries.*.config.tts provider keys via auto-migration", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
plugins: {
|
||||
entries: {
|
||||
"voice-call": {
|
||||
config: {
|
||||
tts: {
|
||||
provider: "openai",
|
||||
openai: {
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "plugins.entries")).toBe(true);
|
||||
const voiceCallTts = (
|
||||
snap.sourceConfig.plugins?.entries as
|
||||
| Record<
|
||||
string,
|
||||
{
|
||||
config?: {
|
||||
tts?: {
|
||||
providers?: Record<string, unknown>;
|
||||
openai?: unknown;
|
||||
};
|
||||
};
|
||||
}
|
||||
>
|
||||
| undefined
|
||||
)?.["voice-call"]?.config?.tts;
|
||||
expect(voiceCallTts?.providers?.openai).toEqual({
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
});
|
||||
expect(voiceCallTts?.openai).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts legacy discord voice tts provider keys via auto-migration and reports legacyIssues", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
channels: {
|
||||
discord: {
|
||||
voice: {
|
||||
tts: {
|
||||
provider: "elevenlabs",
|
||||
elevenlabs: {
|
||||
voiceId: "voice-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
main: {
|
||||
voice: {
|
||||
tts: {
|
||||
edge: {
|
||||
voice: "en-US-AvaNeural",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.voice.tts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "channels.discord.accounts")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(snap.sourceConfig.channels?.discord?.voice?.tts?.providers?.elevenlabs).toEqual({
|
||||
voiceId: "voice-1",
|
||||
});
|
||||
expect(
|
||||
snap.sourceConfig.channels?.discord?.accounts?.main?.voice?.tts?.providers?.microsoft,
|
||||
).toEqual({
|
||||
voice: "en-US-AvaNeural",
|
||||
});
|
||||
expect(
|
||||
(snap.sourceConfig.channels?.discord?.voice?.tts as Record<string, unknown> | undefined)
|
||||
?.elevenlabs,
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
(
|
||||
snap.sourceConfig.channels?.discord?.accounts?.main?.voice?.tts as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
)?.edge,
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not treat resolved-only gateway.bind aliases as source-literal legacy or invalid", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
gateway: { bind: "${OPENCLAW_BIND}" },
|
||||
});
|
||||
|
||||
const prev = process.env.OPENCLAW_BIND;
|
||||
process.env.OPENCLAW_BIND = "0.0.0.0";
|
||||
try {
|
||||
const snap = await readConfigFileSnapshot();
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues).toHaveLength(0);
|
||||
expect(snap.issues).toHaveLength(0);
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env.OPENCLAW_BIND;
|
||||
} else {
|
||||
process.env.OPENCLAW_BIND = prev;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("still marks literal gateway.bind host aliases as legacy", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
await writeOpenClawConfig(home, {
|
||||
gateway: { bind: "0.0.0.0" },
|
||||
});
|
||||
|
||||
const snap = await readConfigFileSnapshot();
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.legacyIssues.some((issue) => issue.path === "gateway.bind")).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user