diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d7b81dac31..b1110eeb925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -265,6 +265,7 @@ Docs: https://docs.openclaw.ai - Config/validation log sanitization: sanitize config-validation issue paths/messages before logging so control characters and ANSI escape sequences cannot inject misleading terminal output from crafted config content. (#39116) Thanks @powermaster888. - Agents/compaction counter accuracy: count successful overflow-triggered auto-compactions (`willRetry=true`) in the compaction counter while still excluding aborted/no-result events, so `/status` reflects actual safeguard compaction activity. (#39123) Thanks @MumuTW. - Gateway/chat delta ordering: flush buffered assistant deltas before emitting tool `start` events so pre-tool text is delivered to Control UI before tool cards, avoiding transient text/tool ordering artifacts in streaming. (#39128) Thanks @0xtangping. +- Voice-call plugin schema parity: add missing manifest `configSchema` fields (`webhookSecurity`, `streaming.preStartTimeoutMs|maxPendingConnections|maxPendingConnectionsPerIp|maxConnections`, `staleCallReaperSeconds`) so gateway AJV validation accepts already-supported runtime config instead of failing with `additionalProperties` errors. (#38892) Thanks @giumex. ## 2026.3.2 diff --git a/extensions/voice-call/openclaw.plugin.json b/extensions/voice-call/openclaw.plugin.json index 04f50218fa6..d9a904c73eb 100644 --- a/extensions/voice-call/openclaw.plugin.json +++ b/extensions/voice-call/openclaw.plugin.json @@ -249,6 +249,10 @@ "type": "integer", "minimum": 1 }, + "staleCallReaperSeconds": { + "type": "integer", + "minimum": 0 + }, "silenceTimeoutMs": { "type": "integer", "minimum": 1 @@ -313,6 +317,27 @@ } } }, + "webhookSecurity": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowedHosts": { + "type": "array", + "items": { + "type": "string" + } + }, + "trustForwardingHeaders": { + "type": "boolean" + }, + "trustedProxyIPs": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "streaming": { "type": "object", "additionalProperties": false, @@ -341,6 +366,22 @@ }, "streamPath": { "type": "string" + }, + "preStartTimeoutMs": { + "type": "integer", + "minimum": 1 + }, + "maxPendingConnections": { + "type": "integer", + "minimum": 1 + }, + "maxPendingConnectionsPerIp": { + "type": "integer", + "minimum": 1 + }, + "maxConnections": { + "type": "integer", + "minimum": 1 } } }, diff --git a/src/config/config.plugin-validation.test.ts b/src/config/config.plugin-validation.test.ts index 6c0b9e56587..02eab6789ea 100644 --- a/src/config/config.plugin-validation.test.ts +++ b/src/config/config.plugin-validation.test.ts @@ -37,6 +37,7 @@ describe("config plugin validation", () => { let badPluginDir = ""; let enumPluginDir = ""; let bluebubblesPluginDir = ""; + let voiceCallSchemaPluginDir = ""; const envSnapshot = { OPENCLAW_STATE_DIR: process.env.OPENCLAW_STATE_DIR, OPENCLAW_PLUGIN_MANIFEST_CACHE_MS: process.env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS, @@ -83,6 +84,24 @@ describe("config plugin validation", () => { channels: ["bluebubbles"], schema: { type: "object" }, }); + voiceCallSchemaPluginDir = path.join(suiteHome, "voice-call-schema-plugin"); + const voiceCallManifestPath = path.join( + process.cwd(), + "extensions", + "voice-call", + "openclaw.plugin.json", + ); + const voiceCallManifest = JSON.parse(await fs.readFile(voiceCallManifestPath, "utf-8")) as { + configSchema?: Record; + }; + if (!voiceCallManifest.configSchema) { + throw new Error("voice-call manifest missing configSchema"); + } + await writePluginFixture({ + dir: voiceCallSchemaPluginDir, + id: "voice-call-schema-fixture", + schema: voiceCallManifest.configSchema, + }); process.env.OPENCLAW_STATE_DIR = path.join(suiteHome, ".openclaw"); process.env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS = "10000"; clearPluginManifestRegistryCache(); @@ -91,7 +110,7 @@ describe("config plugin validation", () => { validateInSuite({ plugins: { enabled: false, - load: { paths: [badPluginDir, bluebubblesPluginDir] }, + load: { paths: [badPluginDir, bluebubblesPluginDir, voiceCallSchemaPluginDir] }, }, }); }); @@ -229,6 +248,37 @@ describe("config plugin validation", () => { } }); + it("accepts voice-call webhookSecurity and streaming guard config fields", async () => { + const res = validateInSuite({ + agents: { list: [{ id: "pi" }] }, + plugins: { + enabled: true, + load: { paths: [voiceCallSchemaPluginDir] }, + entries: { + "voice-call-schema-fixture": { + config: { + provider: "twilio", + webhookSecurity: { + allowedHosts: ["voice.example.com"], + trustForwardingHeaders: false, + trustedProxyIPs: ["127.0.0.1"], + }, + streaming: { + enabled: true, + preStartTimeoutMs: 5000, + maxPendingConnections: 16, + maxPendingConnectionsPerIp: 4, + maxConnections: 64, + }, + staleCallReaperSeconds: 180, + }, + }, + }, + }, + }); + expect(res.ok).toBe(true); + }); + it("accepts known plugin ids and valid channel/heartbeat enums", async () => { const res = validateInSuite({ agents: {