diff --git a/extensions/canvas/openclaw.plugin.json b/extensions/canvas/openclaw.plugin.json index b071fc2155f1..942dec848c70 100644 --- a/extensions/canvas/openclaw.plugin.json +++ b/extensions/canvas/openclaw.plugin.json @@ -16,6 +16,30 @@ "configContracts": { "compatibilityMigrationPaths": ["canvasHost"] }, + "uiHints": { + "host": { + "label": "Canvas Host", + "help": "Serves local Canvas and A2UI files for paired nodes.", + "advanced": true + }, + "host.enabled": { + "label": "Canvas Host Enabled", + "advanced": true + }, + "host.root": { + "label": "Canvas Host Root Directory", + "help": "Directory to serve. Defaults to the OpenClaw state canvas directory.", + "advanced": true + }, + "host.port": { + "label": "Canvas Host Port", + "advanced": true + }, + "host.liveReload": { + "label": "Canvas Host Live Reload", + "advanced": true + } + }, "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/canvas/src/config.test.ts b/extensions/canvas/src/config.test.ts index 0ae1a15c9ab6..33263dccb3d0 100644 --- a/extensions/canvas/src/config.test.ts +++ b/extensions/canvas/src/config.test.ts @@ -1,6 +1,8 @@ // Canvas tests cover config plugin behavior. +import { readFileSync } from "node:fs"; import { afterEach, describe, expect, it } from "vitest"; import { + canvasConfigSchema, isCanvasHostEnabled, isCanvasPluginEnabled, parseCanvasPluginConfig, @@ -18,6 +20,38 @@ describe("Canvas plugin config", () => { } }); + it("keeps host config presentation metadata manifest-owned", () => { + const manifest = JSON.parse( + readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"), + ) as { uiHints?: Record> }; + + expect(canvasConfigSchema).not.toHaveProperty("uiHints"); + expect(manifest.uiHints).toEqual({ + host: { + label: "Canvas Host", + help: "Serves local Canvas and A2UI files for paired nodes.", + advanced: true, + }, + "host.enabled": { + label: "Canvas Host Enabled", + advanced: true, + }, + "host.root": { + label: "Canvas Host Root Directory", + help: "Directory to serve. Defaults to the OpenClaw state canvas directory.", + advanced: true, + }, + "host.port": { + label: "Canvas Host Port", + advanced: true, + }, + "host.liveReload": { + label: "Canvas Host Live Reload", + advanced: true, + }, + }); + }); + it("parses host config from the plugin entry", () => { expect( parseCanvasPluginConfig({ diff --git a/extensions/canvas/src/config.ts b/extensions/canvas/src/config.ts index 726248e9c26c..8a0cef850fc8 100644 --- a/extensions/canvas/src/config.ts +++ b/extensions/canvas/src/config.ts @@ -29,7 +29,6 @@ export type CanvasPluginConfig = { type CanvasPluginConfigSchema = { parse: (value: unknown) => CanvasPluginConfig; - uiHints: Record; }; function readPositiveInteger(value: unknown): number | undefined { @@ -97,31 +96,7 @@ export function isCanvasHostEnabled(config?: OpenClawConfig): boolean { return resolveCanvasHostConfig({ config }).enabled !== false; } -/** Config schema metadata for Canvas plugin settings. */ +/** Runtime config parser for Canvas plugin settings. */ export const canvasConfigSchema: CanvasPluginConfigSchema = { parse: parseCanvasPluginConfig, - uiHints: { - host: { - label: "Canvas Host", - help: "Serves local Canvas and A2UI files for paired nodes.", - advanced: true, - }, - "host.enabled": { - label: "Canvas Host Enabled", - advanced: true, - }, - "host.root": { - label: "Canvas Host Root Directory", - help: "Directory to serve. Defaults to the OpenClaw state canvas directory.", - advanced: true, - }, - "host.port": { - label: "Canvas Host Port", - advanced: true, - }, - "host.liveReload": { - label: "Canvas Host Live Reload", - advanced: true, - }, - }, }; diff --git a/extensions/cua-computer/index.test.ts b/extensions/cua-computer/index.test.ts index c9ca25d44484..95276981a739 100644 --- a/extensions/cua-computer/index.test.ts +++ b/extensions/cua-computer/index.test.ts @@ -50,6 +50,7 @@ describe("cua-computer plugin registration", () => { expect(validateManifestConfig(config).ok).toBe(true); expect(plugin.configSchema.safeParse?.({ unexpected: true }).success).toBe(false); expect(validateManifestConfig({ unexpected: true }).ok).toBe(false); + expect(plugin.configSchema).not.toHaveProperty("uiHints"); const commands: OpenClawPluginNodeHostCommand[] = []; plugin.register({ diff --git a/extensions/cua-computer/index.ts b/extensions/cua-computer/index.ts index 606d06c9499a..156f9e6dd723 100644 --- a/extensions/cua-computer/index.ts +++ b/extensions/cua-computer/index.ts @@ -8,9 +8,7 @@ const CuaComputerConfigSchema = z.strictObject({ driverPath: z.string().optional(), }); -const configSchema = buildPluginConfigSchema(CuaComputerConfigSchema, { - uiHints: {}, -}); +const configSchema = buildPluginConfigSchema(CuaComputerConfigSchema); export default definePluginEntry({ id: "cua-computer", diff --git a/extensions/google-meet/index.test.ts b/extensions/google-meet/index.test.ts index 53321307d1cc..e4682eb45857 100644 --- a/extensions/google-meet/index.test.ts +++ b/extensions/google-meet/index.test.ts @@ -1240,7 +1240,7 @@ describe("google-meet plugin", () => { await transcriptionHandle.stop(); }); - it("declares advanced config metadata in the plugin entry and manifest", () => { + it("keeps advanced config metadata manifest-owned", () => { const manifest = JSON.parse( readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), ) as { @@ -1248,11 +1248,9 @@ describe("google-meet plugin", () => { configSchema?: GoogleMeetManifestConfigSchema; }; const configSchema = requireGoogleMeetManifestConfigSchema(manifest); - const entry = plugin as unknown as { - configSchema: { - uiHints?: Record; - }; - }; + const entry = plugin as unknown as { configSchema: Record }; + + expect(entry.configSchema).not.toHaveProperty("uiHints"); for (const key of [ "chrome.audioBufferBytes", @@ -1263,7 +1261,6 @@ describe("google-meet plugin", () => { "voiceCall.dtmfDelayMs", "voiceCall.postDtmfSpeechDelayMs", ]) { - expect(entry.configSchema.uiHints?.[key]).toHaveProperty("advanced", true); expect(manifest.uiHints?.[key]).toHaveProperty("advanced", true); } const chromeProperties = configSchema.properties?.chrome?.properties; diff --git a/extensions/google-meet/src/plugin-schema.ts b/extensions/google-meet/src/plugin-schema.ts index 1ef3272c370b..f72ce0dc5a23 100644 --- a/extensions/google-meet/src/plugin-schema.ts +++ b/extensions/google-meet/src/plugin-schema.ts @@ -6,175 +6,6 @@ export const googleMeetConfigSchema = { parse(value: unknown) { return resolveGoogleMeetConfig(value); }, - uiHints: { - "defaults.meeting": { - label: "Default Meeting", - help: "Meet URL, meeting code, or spaces/{id} used when CLI commands omit a meeting.", - }, - "preview.enrollmentAcknowledged": { - label: "Preview Acknowledged", - help: "Confirms you understand the Google Meet Media API is still Developer Preview.", - advanced: true, - }, - defaultTransport: { - label: "Default Transport", - help: "Chrome uses a signed-in browser profile. Chrome-node runs Chrome on a paired node. Twilio uses Meet dial-in numbers.", - }, - defaultMode: { - label: "Default Mode", - help: "Agent uses realtime transcription plus regular OpenClaw TTS. Bidi uses the realtime voice model directly. Transcribe observes only.", - }, - "chrome.audioBackend": { - label: "Chrome Audio Backend", - help: "Auto selects BlackHole 2ch on macOS or PipeWire-Pulse on Linux.", - }, - "chrome.launch": { label: "Launch Chrome" }, - "chrome.browserProfile": { label: "Chrome Profile", advanced: true }, - "chrome.guestName": { - label: "Guest Name", - help: "Used when Chrome lands on the signed-out Meet guest-name screen.", - }, - "chrome.reuseExistingTab": { - label: "Reuse Existing Meet Tab", - help: "Avoids opening duplicate tabs for the same Meet URL.", - }, - "chrome.autoJoin": { - label: "Auto Join Guest Screen", - help: "Best-effort guest-name fill and Join Now click through OpenClaw browser automation.", - }, - "chrome.waitForInCallMs": { - label: "Wait For In-Call (ms)", - help: "Waits for Chrome to report that the Meet tab is in-call before the realtime intro speaks.", - advanced: true, - }, - "chrome.audioFormat": { - label: "Audio Format", - help: "Command-pair audio format. PCM16 24 kHz is the default Chrome/Meet path; G.711 mu-law 8 kHz remains available for legacy command pairs.", - advanced: true, - }, - "chrome.audioBufferBytes": { - label: "Audio Buffer Bytes", - help: "Processing buffer for generated Chrome command-pair audio. Lower values reduce latency but may underrun on busy hosts.", - advanced: true, - }, - "chrome.audioInputCommand": { - label: "Audio Input Command", - help: "Command that writes meeting audio to stdout in chrome.audioFormat.", - advanced: true, - }, - "chrome.audioOutputCommand": { - label: "Audio Output Command", - help: "Command that reads assistant audio from stdin in chrome.audioFormat.", - advanced: true, - }, - "chrome.bargeInInputCommand": { - label: "Barge-In Input Command", - help: "Optional Gateway-hosted microphone command that writes signed 16-bit little-endian mono PCM for human interruption detection while assistant playback is active.", - advanced: true, - }, - "chrome.bargeInRmsThreshold": { - label: "Barge-In RMS Threshold", - help: "RMS level on chrome.bargeInInputCommand that counts as a human interruption.", - advanced: true, - }, - "chrome.bargeInPeakThreshold": { - label: "Barge-In Peak Threshold", - help: "Peak level on chrome.bargeInInputCommand that counts as a human interruption.", - advanced: true, - }, - "chrome.bargeInCooldownMs": { - label: "Barge-In Cooldown (ms)", - help: "Minimum delay between repeated barge-in clears.", - advanced: true, - }, - "chrome.audioBridgeCommand": { label: "Audio Bridge Command", advanced: true }, - "chrome.audioBridgeHealthCommand": { - label: "Audio Bridge Health Command", - advanced: true, - }, - "chromeNode.node": { - label: "Chrome Node", - help: "Node id/name/IP that owns Chrome and the native virtual-audio backend for chrome-node transport.", - advanced: true, - }, - "twilio.defaultDialInNumber": { - label: "Default Dial-In Number", - placeholder: "+15551234567", - }, - "twilio.defaultPin": { label: "Default PIN", advanced: true }, - "twilio.defaultDtmfSequence": { label: "Default DTMF Sequence", advanced: true }, - "voiceCall.enabled": { label: "Delegate To Voice Call" }, - "voiceCall.gatewayUrl": { label: "Voice Call Gateway URL", advanced: true }, - "voiceCall.token": { - label: "Voice Call Gateway Token", - sensitive: true, - advanced: true, - }, - "voiceCall.requestTimeoutMs": { - label: "Voice Call Request Timeout (ms)", - advanced: true, - }, - "voiceCall.dtmfDelayMs": { - label: "DTMF Wait Before PIN (ms)", - help: "Leading Twilio wait time before playing a PIN-derived Meet DTMF sequence. Increase it if Meet asks for the PIN after DTMF was sent.", - advanced: true, - }, - "voiceCall.postDtmfSpeechDelayMs": { - label: "Post-DTMF Speech Delay (ms)", - help: "Delay before requesting the realtime intro greeting after Voice Call starts the Twilio leg.", - advanced: true, - }, - "voiceCall.introMessage": { label: "Voice Call Intro Message", advanced: true }, - "realtime.strategy": { - label: "Realtime Strategy", - help: "Legacy realtime alias setting. Use mode=agent or mode=bidi for new Meet joins.", - }, - "realtime.provider": { - label: "Speech Provider", - help: "Compatibility fallback for both realtime transcription and bidi voice. Prefer realtime.transcriptionProvider and realtime.voiceProvider for new configs.", - }, - "realtime.transcriptionProvider": { - label: "Realtime Transcription Provider", - help: "Agent mode uses this provider to transcribe meeting audio before regular OpenClaw TTS answers.", - }, - "realtime.voiceProvider": { - label: "Bidi Voice Provider", - help: "Bidi mode uses this realtime voice provider. Falls back to realtime.provider when unset.", - }, - "realtime.model": { - label: "Bidi Realtime Model", - help: "Only used by mode=bidi. Agent mode answers with the configured OpenClaw agent and regular TTS.", - advanced: true, - }, - "realtime.instructions": { label: "Realtime Instructions", advanced: true }, - "realtime.introMessage": { - label: "Realtime Intro Message", - help: "Spoken once when the realtime bridge is ready. Set to an empty string to join silently.", - }, - "realtime.agentId": { - label: "Realtime Consult Agent", - help: 'OpenClaw agent id used by openclaw_agent_consult. Defaults to "main".', - advanced: true, - }, - "realtime.toolPolicy": { - label: "Realtime Tool Policy", - help: "Safe read-only tools are available by default; owner requests can unlock broader tools.", - advanced: true, - }, - "oauth.clientId": { label: "OAuth Client ID" }, - "oauth.clientSecret": { label: "OAuth Client Secret", sensitive: true }, - "oauth.refreshToken": { label: "OAuth Refresh Token", sensitive: true }, - "oauth.accessToken": { - label: "Cached Access Token", - sensitive: true, - advanced: true, - }, - "oauth.expiresAt": { - label: "Cached Access Token Expiry", - help: "Unix epoch milliseconds used only for the cached access-token fast path.", - advanced: true, - }, - }, }; export const GoogleMeetToolSchema = Type.Object({ diff --git a/extensions/linux-node/src/config.test.ts b/extensions/linux-node/src/config.test.ts index 31c6c52c652a..fe2b394cd7df 100644 --- a/extensions/linux-node/src/config.test.ts +++ b/extensions/linux-node/src/config.test.ts @@ -28,7 +28,8 @@ describe("linux-node config", () => { }); it("exports the same strict shape through the plugin schema", () => { - const safeParse = createLinuxNodePluginConfigSchema().safeParse; + const schema = createLinuxNodePluginConfigSchema(); + const safeParse = schema.safeParse; if (!safeParse) { throw new Error("missing config schema validator"); } @@ -37,5 +38,6 @@ describe("linux-node config", () => { unexpected: true, }); expect(result.success).toBe(false); + expect(schema).not.toHaveProperty("uiHints"); }); }); diff --git a/extensions/linux-node/src/config.ts b/extensions/linux-node/src/config.ts index 355410b4eee8..1fdfd13373f7 100644 --- a/extensions/linux-node/src/config.ts +++ b/extensions/linux-node/src/config.ts @@ -19,22 +19,7 @@ export type ResolvedLinuxNodePluginConfig = { }; export function createLinuxNodePluginConfigSchema() { - return buildPluginConfigSchema(LinuxNodePluginConfigSchema, { - uiHints: { - "notify.enabled": { - label: "Desktop Notifications", - help: "Expose system.notify when notify-send is installed. Enabled by default.", - }, - "camera.enabled": { - label: "Camera", - help: "Expose camera commands when FFmpeg is installed. Requires a node service restart.", - }, - "location.enabled": { - label: "Location", - help: "Expose location.get when the GeoClue where-am-i demo is installed. Requires a node service restart.", - }, - }, - }); + return buildPluginConfigSchema(LinuxNodePluginConfigSchema); } export function resolveLinuxNodePluginConfig(value: unknown): ResolvedLinuxNodePluginConfig { diff --git a/extensions/memory-lancedb/config.test.ts b/extensions/memory-lancedb/config.test.ts index 139f61b255c2..509ed511b3d3 100644 --- a/extensions/memory-lancedb/config.test.ts +++ b/extensions/memory-lancedb/config.test.ts @@ -9,9 +9,17 @@ import { memoryConfigSchema } from "./config.js"; const manifest = JSON.parse( fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf-8"), -) as { configSchema: JsonSchemaObject }; +) as { configSchema: JsonSchemaObject; uiHints?: Record }; describe("memory-lancedb config", () => { + it("keeps config presentation metadata manifest-owned", () => { + expect(memoryConfigSchema).not.toHaveProperty("uiHints"); + expect(manifest.uiHints?.["embedding.apiKey"]).toMatchObject({ + label: "Embedding API Key", + sensitive: true, + }); + }); + it("accepts dreaming in the manifest schema and preserves it in runtime parsing", () => { const manifestResult = validateJsonSchemaValue({ schema: manifest.configSchema, diff --git a/extensions/memory-lancedb/config.ts b/extensions/memory-lancedb/config.ts index bb8fd9a28c87..5f5234ce70c8 100644 --- a/extensions/memory-lancedb/config.ts +++ b/extensions/memory-lancedb/config.ts @@ -226,71 +226,4 @@ export const memoryConfigSchema = { ...(storageOptions ? { storageOptions } : {}), }; }, - uiHints: { - "embedding.provider": { - label: "Embedding Provider", - placeholder: "openai", - help: "Memory embedding provider adapter to use (for example openai, github-copilot, ollama)", - }, - "embedding.apiKey": { - label: "OpenAI API Key", - sensitive: true, - placeholder: "sk-proj-...", - help: "Optional API key override for OpenAI-compatible embeddings; omit to use configured provider auth", - }, - "embedding.baseUrl": { - label: "Base URL", - placeholder: "https://api.openai.com/v1", - help: "Optional provider or OpenAI-compatible embedding endpoint base URL", - advanced: true, - }, - "embedding.dimensions": { - label: "Dimensions", - placeholder: "1536", - help: "Vector dimensions for custom models (required for non-standard models)", - advanced: true, - }, - "embedding.model": { - label: "Embedding Model", - placeholder: DEFAULT_MODEL, - help: "OpenAI embedding model to use", - }, - dbPath: { - label: "Database Path", - placeholder: "~/.openclaw/memory/lancedb", - advanced: true, - help: "Local filesystem path or cloud storage URI (s3://, gs://) for LanceDB database", - }, - autoCapture: { - label: "Auto-Capture", - help: "Automatically capture important information from conversations", - }, - autoRecall: { - label: "Auto-Recall", - help: "Automatically inject relevant memories into context", - }, - captureMaxChars: { - label: "Capture Max Chars", - help: "Maximum message length eligible for auto-capture", - advanced: true, - placeholder: String(DEFAULT_CAPTURE_MAX_CHARS), - }, - customTriggers: { - label: "Custom Triggers", - help: "Literal phrases that should make auto-capture consider a message memory-worthy", - advanced: true, - }, - recallMaxChars: { - label: "Recall Query Max Chars", - help: "Maximum prompt/query length embedded for memory recall. Lower for small local embedding models.", - advanced: true, - placeholder: String(DEFAULT_RECALL_MAX_CHARS), - }, - storageOptions: { - label: "Storage Options", - sensitive: true, - advanced: true, - help: "Storage configuration options (access_key, secret_key, endpoint, etc.); supports ${ENV_VAR} values", - }, - }, }; diff --git a/extensions/voice-call/index.test.ts b/extensions/voice-call/index.test.ts index d1beb1f8ef04..a56b4ff7e200 100644 --- a/extensions/voice-call/index.test.ts +++ b/extensions/voice-call/index.test.ts @@ -260,6 +260,33 @@ describe("voice-call plugin", () => { ]; }); + it("keeps config presentation metadata manifest-owned", () => { + const manifest = JSON.parse( + fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"), + ) as { + uiHints?: Record>; + configSchema?: { properties?: Record }; + }; + const entry = plugin as unknown as { configSchema: Record }; + + expect(entry.configSchema).not.toHaveProperty("uiHints"); + expect(manifest.uiHints?.agentId).toEqual({ + label: "Response Agent ID", + help: 'Agent workspace used for voice response generation. Defaults to "main".', + advanced: true, + }); + expect(manifest.configSchema?.properties?.agentId).toEqual({ + type: "string", + minLength: 1, + }); + expect(manifest.uiHints?.["realtime.consultThinkingLevel"]).toHaveProperty("advanced", true); + expect(manifest.uiHints?.["realtime.consultFastMode"]).toHaveProperty("advanced", true); + expect(manifest.uiHints?.sessionScope).toMatchObject({ + label: "Session Scope", + help: expect.stringContaining("per-phone"), + }); + }); + it("defaults canonical plugin config to an enabled mock runtime", async () => { const { service } = setup({}); diff --git a/extensions/voice-call/index.ts b/extensions/voice-call/index.ts index 3806dd590df7..25a897bdf647 100644 --- a/extensions/voice-call/index.ts +++ b/extensions/voice-call/index.ts @@ -43,146 +43,6 @@ const voiceCallConfigSchema = { provider: config.provider ?? (enabled ? "mock" : undefined), }); }, - uiHints: { - provider: { - label: "Provider", - help: "Use twilio, telnyx, or mock for dev/no-network.", - }, - fromNumber: { label: "From Number", placeholder: "+15550001234" }, - toNumber: { label: "Default To Number", placeholder: "+15550001234" }, - inboundPolicy: { label: "Inbound Policy" }, - allowFrom: { label: "Inbound Allowlist" }, - inboundGreeting: { label: "Inbound Greeting", advanced: true }, - numbers: { - label: "Per-number Routing", - help: "Inbound overrides keyed by dialed E.164 number.", - advanced: true, - }, - "telnyx.apiKey": { label: "Telnyx API Key", sensitive: true }, - "telnyx.connectionId": { label: "Telnyx Connection ID" }, - "telnyx.publicKey": { label: "Telnyx Public Key", sensitive: true }, - "twilio.accountSid": { label: "Twilio Account SID" }, - "twilio.authToken": { label: "Twilio Auth Token", sensitive: true }, - "twilio.region": { label: "Twilio Region", advanced: true }, - "outbound.defaultMode": { label: "Default Call Mode" }, - "outbound.notifyHangupDelaySec": { - label: "Notify Hangup Delay (sec)", - advanced: true, - }, - "serve.port": { label: "Webhook Port" }, - "serve.bind": { label: "Webhook Bind" }, - "serve.path": { label: "Webhook Path" }, - "tailscale.mode": { label: "Tailscale Mode", advanced: true }, - "tailscale.path": { label: "Tailscale Path", advanced: true }, - "tunnel.provider": { label: "Tunnel Provider", advanced: true }, - "tunnel.ngrokAuthToken": { - label: "ngrok Auth Token", - sensitive: true, - advanced: true, - }, - "tunnel.ngrokDomain": { label: "ngrok Domain", advanced: true }, - "tunnel.allowNgrokFreeTierLoopbackBypass": { - label: "Allow ngrok Free Tier (Loopback Bypass)", - advanced: true, - }, - "streaming.enabled": { - label: "Enable Streaming", - help: "Classic streaming transcription currently requires the Twilio call provider.", - advanced: true, - }, - "streaming.provider": { - label: "Streaming Provider", - help: "Uses the first registered realtime transcription provider when unset.", - advanced: true, - }, - "streaming.providers": { label: "Streaming Provider Config", advanced: true }, - "streaming.streamPath": { label: "Media Stream Path", advanced: true }, - "realtime.enabled": { label: "Enable Realtime Voice", advanced: true }, - "realtime.provider": { - label: "Realtime Voice Provider", - help: "Uses the first registered realtime voice provider when unset.", - advanced: true, - }, - "realtime.streamPath": { label: "Realtime Stream Path", advanced: true }, - "realtime.instructions": { label: "Realtime Instructions", advanced: true }, - "realtime.toolPolicy": { - label: "Realtime Tool Policy", - help: "Controls the shared openclaw_agent_consult tool.", - advanced: true, - }, - "realtime.consultPolicy": { - label: "Realtime Consult Policy", - help: "Guides when the realtime voice model should call openclaw_agent_consult.", - advanced: true, - }, - "realtime.fastContext.enabled": { - label: "Enable Fast Realtime Context", - help: "Searches memory/session context before the full consult agent.", - advanced: true, - }, - "realtime.fastContext.timeoutMs": { - label: "Fast Context Timeout", - advanced: true, - }, - "realtime.fastContext.maxResults": { - label: "Fast Context Result Limit", - advanced: true, - }, - "realtime.fastContext.sources": { - label: "Fast Context Sources", - advanced: true, - }, - "realtime.fastContext.fallbackToConsult": { - label: "Fallback To Full Consult", - advanced: true, - }, - "realtime.agentContext.enabled": { - label: "Enable Agent Voice Context", - help: "Injects a compact agent identity and workspace context capsule into realtime voice instructions.", - advanced: true, - }, - "realtime.agentContext.maxChars": { - label: "Agent Voice Context Limit", - advanced: true, - }, - "realtime.agentContext.includeIdentity": { - label: "Include Agent Identity", - advanced: true, - }, - "realtime.agentContext.includeWorkspaceFiles": { - label: "Include Agent Workspace Files", - advanced: true, - }, - "realtime.agentContext.files": { - label: "Agent Voice Context Files", - advanced: true, - }, - "realtime.providers": { label: "Realtime Provider Config", advanced: true }, - "tts.provider": { - label: "TTS Provider Override", - help: "Deep-merges with tts (Microsoft is ignored for calls).", - advanced: true, - }, - "tts.providers": { label: "TTS Provider Config", advanced: true }, - publicUrl: { label: "Public Webhook URL", advanced: true }, - skipSignatureVerification: { - label: "Skip Signature Verification", - advanced: true, - }, - store: { label: "Call Log Store Path", advanced: true }, - agentId: { - label: "Response Agent ID", - help: 'Agent workspace used for voice response generation. Defaults to "main".', - advanced: true, - }, - responseModel: { - label: "Response Model", - help: "Optional override. Falls back to the runtime default model when unset.", - advanced: true, - }, - responseSystemPrompt: { label: "Response System Prompt", advanced: true }, - responseTimeoutMs: { label: "Response Timeout (ms)", advanced: true }, - }, }; const VoiceCallToolSchema = Type.Union([ diff --git a/extensions/voice-call/openclaw.plugin.json b/extensions/voice-call/openclaw.plugin.json index 8b0469c72491..ea63e2ef8ff4 100644 --- a/extensions/voice-call/openclaw.plugin.json +++ b/extensions/voice-call/openclaw.plugin.json @@ -228,6 +228,11 @@ "label": "Call Log Store Path", "advanced": true }, + "agentId": { + "label": "Response Agent ID", + "help": "Agent workspace used for voice response generation. Defaults to \"main\".", + "advanced": true + }, "sessionScope": { "label": "Session Scope", "help": "Use per-phone to preserve caller memory across calls, or per-call to isolate every call into a fresh voice session." @@ -883,6 +888,10 @@ "store": { "type": "string" }, + "agentId": { + "type": "string", + "minLength": 1 + }, "sessionScope": { "type": "string", "enum": ["per-phone", "per-call"] diff --git a/src/plugins/config-schema.ts b/src/plugins/config-schema.ts index db336000c533..7470edc49ffd 100644 --- a/src/plugins/config-schema.ts +++ b/src/plugins/config-schema.ts @@ -17,12 +17,14 @@ type ZodSchemaWithToJsonSchema = ZodTypeAny & { }; type BuildPluginConfigSchemaOptions = { + /** @deprecated Declare top-level `uiHints` in `openclaw.plugin.json`. */ uiHints?: Record; safeParse?: OpenClawPluginConfigSchema["safeParse"]; }; type BuildJsonPluginConfigSchemaOptions = { cacheKey?: string; + /** @deprecated Declare top-level `uiHints` in `openclaw.plugin.json`. */ uiHints?: Record; safeParse?: OpenClawPluginConfigSchema["safeParse"]; }; diff --git a/src/plugins/plugin-config-schema.types.ts b/src/plugins/plugin-config-schema.types.ts index 555dc5b3428e..26f6e631fd29 100644 --- a/src/plugins/plugin-config-schema.types.ts +++ b/src/plugins/plugin-config-schema.types.ts @@ -7,8 +7,7 @@ type PluginConfigValidation = { ok: true; value?: unknown } | { ok: false; error * Config schema contract accepted by plugin manifests and runtime registration. * * Plugins can provide a Zod-like parser, a lightweight `validate(...)` - * function, or both. `uiHints` and `jsonSchema` are optional extras for docs, - * forms, and config UIs. + * function, or both. `jsonSchema` is optional runtime schema metadata. */ export type OpenClawPluginConfigSchema = { safeParse?: (value: unknown) => { @@ -20,6 +19,11 @@ export type OpenClawPluginConfigSchema = { }; parse?: (value: unknown) => unknown; validate?: (value: unknown) => PluginConfigValidation; + /** + * @deprecated Declare config presentation metadata in the plugin's + * `openclaw.plugin.json` manifest via top-level `uiHints`. The host reads + * manifest hints and does not consume runtime config-schema hints. + */ uiHints?: Record; jsonSchema?: JsonSchemaObject; };