mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(plugins): make manifests own config ui hints (#121329)
This commit is contained in:
committed by
GitHub
parent
a6c410ef51
commit
620fcd0e27
@@ -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,
|
||||
|
||||
@@ -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<string, Record<string, unknown>> };
|
||||
|
||||
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({
|
||||
|
||||
@@ -29,7 +29,6 @@ export type CanvasPluginConfig = {
|
||||
|
||||
type CanvasPluginConfigSchema = {
|
||||
parse: (value: unknown) => CanvasPluginConfig;
|
||||
uiHints: Record<string, { label: string; help?: string; advanced?: boolean }>;
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
};
|
||||
};
|
||||
const entry = plugin as unknown as { configSchema: Record<string, unknown> };
|
||||
|
||||
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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<string, unknown> };
|
||||
|
||||
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,
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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<string, Record<string, unknown>>;
|
||||
configSchema?: { properties?: Record<string, unknown> };
|
||||
};
|
||||
const entry = plugin as unknown as { configSchema: Record<string, unknown> };
|
||||
|
||||
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({});
|
||||
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -17,12 +17,14 @@ type ZodSchemaWithToJsonSchema = ZodTypeAny & {
|
||||
};
|
||||
|
||||
type BuildPluginConfigSchemaOptions = {
|
||||
/** @deprecated Declare top-level `uiHints` in `openclaw.plugin.json`. */
|
||||
uiHints?: Record<string, PluginConfigUiHint>;
|
||||
safeParse?: OpenClawPluginConfigSchema["safeParse"];
|
||||
};
|
||||
|
||||
type BuildJsonPluginConfigSchemaOptions = {
|
||||
cacheKey?: string;
|
||||
/** @deprecated Declare top-level `uiHints` in `openclaw.plugin.json`. */
|
||||
uiHints?: Record<string, PluginConfigUiHint>;
|
||||
safeParse?: OpenClawPluginConfigSchema["safeParse"];
|
||||
};
|
||||
|
||||
@@ -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<string, PluginConfigUiHint>;
|
||||
jsonSchema?: JsonSchemaObject;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user