fix(config): remove legacy config aliases from public schema

This commit is contained in:
Vincent Koc
2026-04-04 01:23:21 +09:00
parent d59d236a90
commit c7a947dc0a
26 changed files with 1155 additions and 1069 deletions

View File

@@ -1542,6 +1542,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.discord.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.discord.accounts.*.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -2867,10 +2882,7 @@
{
"path": "channels.discord.accounts.*.streaming",
"kind": "channel",
"type": [
"boolean",
"string"
],
"type": "string",
"required": false,
"enumValues": [
"off",
@@ -2883,21 +2895,6 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.discord.accounts.*.streamMode",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"partial",
"block",
"off"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.discord.accounts.*.textChunkLimit",
"kind": "channel",
@@ -3992,6 +3989,21 @@
"help": "Allow Discord to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.discord.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.discord.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -5437,10 +5449,7 @@
{
"path": "channels.discord.streaming",
"kind": "channel",
"type": [
"boolean",
"string"
],
"type": "string",
"required": false,
"enumValues": [
"off",
@@ -5458,26 +5467,6 @@
"help": "Unified Discord stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\". \"progress\" maps to \"partial\" on Discord. Legacy boolean/streamMode keys are auto-mapped.",
"hasChildren": false
},
{
"path": "channels.discord.streamMode",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"partial",
"block",
"off"
],
"deprecated": false,
"sensitive": false,
"tags": [
"channels",
"network"
],
"label": "Discord Stream Mode (Legacy)",
"help": "Legacy Discord preview mode alias (off | partial | block); auto-migrated to channels.discord.streaming.",
"hasChildren": false
},
{
"path": "channels.discord.textChunkLimit",
"kind": "channel",
@@ -9654,6 +9643,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.imessage.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.imessage.accounts.*.dbPath",
"kind": "channel",
@@ -10314,6 +10318,21 @@
"help": "Allow iMessage to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.imessage.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.imessage.dbPath",
"kind": "channel",
@@ -10974,6 +10993,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.irc.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.irc.accounts.*.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -11696,6 +11730,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.irc.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.irc.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -13376,6 +13425,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.defaultAccount",
"kind": "channel",
@@ -13500,6 +13564,104 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.execApprovals",
"kind": "channel",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "channels.matrix.execApprovals.agentFilter",
"kind": "channel",
"type": "array",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "channels.matrix.execApprovals.agentFilter.*",
"kind": "channel",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.execApprovals.approvers",
"kind": "channel",
"type": "array",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "channels.matrix.execApprovals.approvers.*",
"kind": "channel",
"type": [
"number",
"string"
],
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.execApprovals.enabled",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.execApprovals.sessionFilter",
"kind": "channel",
"type": "array",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": true
},
{
"path": "channels.matrix.execApprovals.sessionFilter.*",
"kind": "channel",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.execApprovals.target",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"dm",
"channel",
"both"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.matrix.groupAllowFrom",
"kind": "channel",
@@ -15582,6 +15744,21 @@
"help": "Allow Microsoft Teams to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.msteams.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.msteams.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -16674,6 +16851,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.nextcloud-talk.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.nextcloud-talk.accounts.*.dmHistoryLimit",
"kind": "channel",
@@ -17289,6 +17481,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.nextcloud-talk.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.nextcloud-talk.defaultAccount",
"kind": "channel",
@@ -18905,6 +19112,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.signal.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.signal.accounts.*.defaultTo",
"kind": "channel",
@@ -19041,6 +19263,16 @@
"tags": [],
"hasChildren": true
},
{
"path": "channels.signal.accounts.*.groups.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.signal.accounts.*.groups.*.requireMention",
"kind": "channel",
@@ -19644,6 +19876,21 @@
"help": "Allow Signal to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.signal.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.signal.defaultAccount",
"kind": "channel",
@@ -19796,6 +20043,16 @@
"tags": [],
"hasChildren": true
},
{
"path": "channels.signal.groups.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.signal.groups.*.requireMention",
"kind": "channel",
@@ -20897,6 +21154,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.slack.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.slack.accounts.*.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -21548,10 +21820,7 @@
{
"path": "channels.slack.accounts.*.streaming",
"kind": "channel",
"type": [
"boolean",
"string"
],
"type": "string",
"required": false,
"enumValues": [
"off",
@@ -21564,21 +21833,6 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.slack.accounts.*.streamMode",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"replace",
"status_final",
"append"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.slack.accounts.*.textChunkLimit",
"kind": "channel",
@@ -22377,6 +22631,21 @@
"help": "Allow Slack to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.slack.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.slack.dangerouslyAllowNameMatching",
"kind": "channel",
@@ -23088,10 +23357,7 @@
{
"path": "channels.slack.streaming",
"kind": "channel",
"type": [
"boolean",
"string"
],
"type": "string",
"required": false,
"enumValues": [
"off",
@@ -23109,26 +23375,6 @@
"help": "Unified Slack stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\". Legacy boolean/streamMode keys are auto-mapped.",
"hasChildren": false
},
{
"path": "channels.slack.streamMode",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"replace",
"status_final",
"append"
],
"deprecated": false,
"sensitive": false,
"tags": [
"channels",
"network"
],
"label": "Slack Stream Mode (Legacy)",
"help": "Legacy Slack preview mode alias (replace | status_final | append); auto-migrated to channels.slack.streaming.",
"hasChildren": false
},
{
"path": "channels.slack.textChunkLimit",
"kind": "channel",
@@ -23731,6 +23977,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.customCommands",
"kind": "channel",
@@ -24214,6 +24475,16 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.topics.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.direct.*.topics.*.requireMention",
"kind": "channel",
@@ -24626,6 +24897,16 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.requireMention",
"kind": "channel",
@@ -24929,6 +25210,16 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.topics.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.groups.*.topics.*.requireMention",
"kind": "channel",
@@ -25263,10 +25554,7 @@
{
"path": "channels.telegram.accounts.*.streaming",
"kind": "channel",
"type": [
"boolean",
"string"
],
"type": "string",
"required": false,
"enumValues": [
"off",
@@ -25279,21 +25567,6 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.streamMode",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"off",
"partial",
"block"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.accounts.*.textChunkLimit",
"kind": "channel",
@@ -25888,6 +26161,21 @@
"help": "Allow Telegram to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.telegram.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.customCommands",
"kind": "channel",
@@ -26386,6 +26674,16 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.topics.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.direct.*.topics.*.requireMention",
"kind": "channel",
@@ -26835,6 +27133,16 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.requireMention",
"kind": "channel",
@@ -27138,6 +27446,16 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.topics.*.ingest",
"kind": "channel",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.groups.*.topics.*.requireMention",
"kind": "channel",
@@ -27515,10 +27833,7 @@
{
"path": "channels.telegram.streaming",
"kind": "channel",
"type": [
"boolean",
"string"
],
"type": "string",
"required": false,
"enumValues": [
"off",
@@ -27536,21 +27851,6 @@
"help": "Unified Telegram stream preview mode: \"off\" | \"partial\" | \"block\" | \"progress\" (default: \"partial\"). \"progress\" maps to \"partial\" on Telegram. Legacy boolean/streamMode keys are auto-mapped.",
"hasChildren": false
},
{
"path": "channels.telegram.streamMode",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"off",
"partial",
"block"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.telegram.textChunkLimit",
"kind": "channel",
@@ -28812,6 +29112,21 @@
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.accounts.*.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.accounts.*.debounceMs",
"kind": "channel",
@@ -29504,6 +29819,21 @@
"help": "Allow WhatsApp to write config in response to channel events/commands (default: true).",
"hasChildren": false
},
{
"path": "channels.whatsapp.contextVisibility",
"kind": "channel",
"type": "string",
"required": false,
"enumValues": [
"all",
"allowlist",
"allowlist_quote"
],
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "channels.whatsapp.debounceMs",
"kind": "channel",

View File

@@ -3685,16 +3685,6 @@
"tags": [],
"hasChildren": false
},
{
"path": "agents.defaults.sandbox.perSession",
"kind": "core",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "agents.defaults.sandbox.prune",
"kind": "core",
@@ -6161,16 +6151,6 @@
"tags": [],
"hasChildren": false
},
{
"path": "agents.list.*.sandbox.perSession",
"kind": "core",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "agents.list.*.sandbox.prune",
"kind": "core",
@@ -8764,20 +8744,6 @@
"tags": [],
"hasChildren": false
},
{
"path": "browser.ssrfPolicy.allowPrivateNetwork",
"kind": "core",
"type": "boolean",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"access"
],
"label": "Browser Allow Private Network",
"help": "Legacy alias for browser.ssrfPolicy.dangerouslyAllowPrivateNetwork. Prefer the dangerously-named key so risk intent is explicit.",
"hasChildren": false
},
{
"path": "browser.ssrfPolicy.dangerouslyAllowPrivateNetwork",
"kind": "core",
@@ -14459,7 +14425,7 @@
"advanced"
],
"label": "Enable Status Reactions",
"help": "Enable lifecycle status reactions for Telegram. When enabled, the ack reaction becomes the initial 'queued' state and progresses through thinking, tool, done/error automatically. Default: false.",
"help": "Enable lifecycle status reactions on supported channels. Slack and Discord treat unset as enabled when ack reactions are active; Telegram requires this to be true before lifecycle reactions are used.",
"hasChildren": false
},
{
@@ -16445,7 +16411,7 @@
"access"
],
"label": "Plugin Allowlist",
"help": "Optional allowlist of plugin IDs; when set, only listed plugins are eligible to load. Use this to enforce approved extension inventories in controlled environments.",
"help": "Optional allowlist of plugin IDs; when set, only listed plugins are eligible to load. Configured bundled chat channels can still activate their bundled plugin when the channel is explicitly enabled in config. Use this to enforce approved extension inventories in controlled environments.",
"hasChildren": true
},
{
@@ -18362,55 +18328,6 @@
"help": "Talk-mode voice synthesis settings for voice identity, model selection, output format, and interruption behavior. Use this section to tune human-facing voice UX while controlling latency and cost.",
"hasChildren": true
},
{
"path": "talk.apiKey",
"kind": "core",
"type": [
"object",
"string"
],
"required": false,
"deprecated": false,
"sensitive": true,
"tags": [
"auth",
"media",
"security"
],
"label": "Talk API Key",
"help": "Use this legacy ElevenLabs API key for Talk mode only during migration, and keep secrets in env-backed storage. Prefer talk.providers.elevenlabs.apiKey (fallback: ELEVENLABS_API_KEY).",
"hasChildren": true
},
{
"path": "talk.apiKey.id",
"kind": "core",
"type": "string",
"required": true,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "talk.apiKey.provider",
"kind": "core",
"type": "string",
"required": true,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "talk.apiKey.source",
"kind": "core",
"type": "string",
"required": true,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "talk.interruptOnSpeech",
"kind": "core",
@@ -18425,35 +18342,6 @@
"help": "If true (default), stop assistant speech when the user starts speaking in Talk mode. Keep enabled for conversational turn-taking.",
"hasChildren": false
},
{
"path": "talk.modelId",
"kind": "core",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"media",
"models"
],
"label": "Talk Model ID",
"help": "Legacy ElevenLabs model ID for Talk mode (default: eleven_v3). Prefer talk.providers.elevenlabs.modelId.",
"hasChildren": false
},
{
"path": "talk.outputFormat",
"kind": "core",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"media"
],
"label": "Talk Output Format",
"help": "Use this legacy ElevenLabs output format for Talk mode (for example pcm_44100 or mp3_44100_128) only during migration. Prefer talk.providers.elevenlabs.outputFormat.",
"hasChildren": false
},
{
"path": "talk.provider",
"kind": "core",
@@ -18632,44 +18520,6 @@
"help": "Milliseconds of user silence before Talk mode finalizes and sends the current transcript. Leave unset to keep the platform default pause window (700 ms on macOS and Android, 900 ms on iOS).",
"hasChildren": false
},
{
"path": "talk.voiceAliases",
"kind": "core",
"type": "object",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"media"
],
"label": "Talk Voice Aliases",
"help": "Use this legacy ElevenLabs voice alias map (for example {\"Clawd\":\"EXAVITQu4vr4xnSDxMaL\"}) only during migration. Prefer talk.providers.elevenlabs.voiceAliases.",
"hasChildren": true
},
{
"path": "talk.voiceAliases.*",
"kind": "core",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [],
"hasChildren": false
},
{
"path": "talk.voiceId",
"kind": "core",
"type": "string",
"required": false,
"deprecated": false,
"sensitive": false,
"tags": [
"media"
],
"label": "Talk Voice ID",
"help": "Legacy ElevenLabs default voice ID for Talk mode. Prefer talk.providers.elevenlabs.voiceId.",
"hasChildren": false
},
{
"path": "tools",
"kind": "core",

File diff suppressed because it is too large Load Diff

View File

@@ -276,7 +276,6 @@ export function noteSandboxScopeWarnings(cfg: OpenClawConfig) {
const scope = resolveSandboxScope({
scope: agentSandbox.scope ?? globalSandbox?.scope,
perSession: agentSandbox.perSession ?? globalSandbox?.perSession,
});
if (scope !== "shared") {

View File

@@ -237,7 +237,6 @@ export async function sandboxExplainCommand(
sandbox: {
mode: sandboxCfg.mode,
scope: sandboxCfg.scope,
perSession: sandboxCfg.scope === "session",
workspaceAccess: sandboxCfg.workspaceAccess,
workspaceRoot: sandboxCfg.workspaceRoot,
sessionIsSandboxed,
@@ -286,7 +285,7 @@ export async function sandboxExplainCommand(
lines.push(
` ${key("mode:")} ${value(payload.sandbox.mode)} ${key("scope:")} ${value(
payload.sandbox.scope,
)} ${key("perSession:")} ${bool(payload.sandbox.perSession)}`,
)}`,
);
lines.push(
` ${key("workspaceAccess:")} ${value(

View File

@@ -656,6 +656,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -717,19 +721,8 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
additionalProperties: false,
},
streaming: {
anyOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["off", "partial", "block", "progress"],
},
],
},
streamMode: {
type: "string",
enum: ["partial", "block", "off"],
enum: ["off", "partial", "block", "progress"],
},
draftChunk: {
type: "object",
@@ -1808,6 +1801,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -1869,19 +1866,8 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
additionalProperties: false,
},
streaming: {
anyOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["off", "partial", "block", "progress"],
},
],
},
streamMode: {
type: "string",
enum: ["partial", "block", "off"],
enum: ["off", "partial", "block", "progress"],
},
draftChunk: {
type: "object",
@@ -5011,6 +4997,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -5287,6 +5277,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -5724,6 +5718,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
minimum: 0,
maximum: 9007199254740991,
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
dms: {
type: "object",
propertyNames: {
@@ -6010,6 +6008,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
minimum: 0,
maximum: 9007199254740991,
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
dms: {
type: "object",
propertyNames: {
@@ -6608,6 +6610,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
blockStreaming: {
type: "boolean",
},
@@ -6749,6 +6755,44 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
},
additionalProperties: false,
},
execApprovals: {
type: "object",
properties: {
enabled: {
type: "boolean",
},
approvers: {
type: "array",
items: {
anyOf: [
{
type: "string",
},
{
type: "number",
},
],
},
},
agentFilter: {
type: "array",
items: {
type: "string",
},
},
sessionFilter: {
type: "array",
items: {
type: "string",
},
},
target: {
type: "string",
enum: ["dm", "channel", "both"],
},
},
additionalProperties: false,
},
groups: {
type: "object",
properties: {},
@@ -7665,6 +7709,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
textChunkLimit: {
type: "integer",
exclusiveMinimum: 0,
@@ -8237,6 +8285,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
minimum: 0,
maximum: 9007199254740991,
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
dms: {
type: "object",
propertyNames: {
@@ -8568,6 +8620,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
minimum: 0,
maximum: 9007199254740991,
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
dms: {
type: "object",
propertyNames: {
@@ -9273,6 +9329,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
groups: {
type: "object",
propertyNames: {
@@ -9284,6 +9344,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
tools: {
type: "object",
properties: {
@@ -9585,6 +9648,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
groups: {
type: "object",
propertyNames: {
@@ -9596,6 +9663,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
tools: {
type: "object",
properties: {
@@ -10211,6 +10281,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -10272,23 +10346,12 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
additionalProperties: false,
},
streaming: {
anyOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["off", "partial", "block", "progress"],
},
],
type: "string",
enum: ["off", "partial", "block", "progress"],
},
nativeStreaming: {
type: "boolean",
},
streamMode: {
type: "string",
enum: ["replace", "status_final", "append"],
},
mediaMaxMb: {
type: "number",
exclusiveMinimum: 0,
@@ -11057,6 +11120,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -11118,23 +11185,12 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
additionalProperties: false,
},
streaming: {
anyOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["off", "partial", "block", "progress"],
},
],
type: "string",
enum: ["off", "partial", "block", "progress"],
},
nativeStreaming: {
type: "boolean",
},
streamMode: {
type: "string",
enum: ["replace", "status_final", "append"],
},
mediaMaxMb: {
type: "number",
exclusiveMinimum: 0,
@@ -11864,6 +11920,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
disableAudioPreflight: {
type: "boolean",
},
@@ -11961,6 +12020,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
disableAudioPreflight: {
type: "boolean",
},
@@ -12063,6 +12125,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -12192,6 +12258,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
disableAudioPreflight: {
type: "boolean",
},
@@ -12285,15 +12354,8 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
enum: ["length", "newline"],
},
streaming: {
anyOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["off", "partial", "block", "progress"],
},
],
type: "string",
enum: ["off", "partial", "block", "progress"],
},
blockStreaming: {
type: "boolean",
@@ -12351,10 +12413,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
},
additionalProperties: false,
},
streamMode: {
type: "string",
enum: ["off", "partial", "block"],
},
mediaMaxMb: {
type: "number",
exclusiveMinimum: 0,
@@ -12865,6 +12923,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
disableAudioPreflight: {
type: "boolean",
},
@@ -12962,6 +13023,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
disableAudioPreflight: {
type: "boolean",
},
@@ -13064,6 +13128,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -13193,6 +13261,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
requireMention: {
type: "boolean",
},
ingest: {
type: "boolean",
},
disableAudioPreflight: {
type: "boolean",
},
@@ -13286,15 +13357,8 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
enum: ["length", "newline"],
},
streaming: {
anyOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["off", "partial", "block", "progress"],
},
],
type: "string",
enum: ["off", "partial", "block", "progress"],
},
blockStreaming: {
type: "boolean",
@@ -13352,10 +13416,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
},
additionalProperties: false,
},
streamMode: {
type: "string",
enum: ["off", "partial", "block"],
},
mediaMaxMb: {
type: "number",
exclusiveMinimum: 0,
@@ -14210,6 +14270,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,
@@ -14459,6 +14523,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
type: "string",
enum: ["open", "disabled", "allowlist"],
},
contextVisibility: {
type: "string",
enum: ["all", "allowlist", "allowlist_quote"],
},
historyLimit: {
type: "integer",
minimum: 0,

View File

@@ -209,8 +209,7 @@ export function applyTalkApiKey(config: OpenClawConfig): OpenClawConfig {
}
const existingProviderApiKeyConfigured = hasConfiguredSecretInput(active?.config?.apiKey);
const existingLegacyApiKeyConfigured = hasConfiguredSecretInput(talk?.apiKey);
if (existingProviderApiKeyConfigured || existingLegacyApiKeyConfigured) {
if (existingProviderApiKeyConfigured) {
return normalized;
}
@@ -221,7 +220,6 @@ export function applyTalkApiKey(config: OpenClawConfig): OpenClawConfig {
const nextTalk = {
...talk,
apiKey: resolved,
providers,
};

View File

@@ -428,9 +428,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
ssrfPolicy: {
type: "object",
properties: {
allowPrivateNetwork: {
type: "boolean",
},
dangerouslyAllowPrivateNetwork: {
type: "boolean",
},
@@ -4064,9 +4061,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
},
],
},
perSession: {
type: "boolean",
},
workspaceRoot: {
type: "string",
},
@@ -5248,9 +5242,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
},
],
},
perSession: {
type: "boolean",
},
workspaceRoot: {
type: "string",
},
@@ -18368,90 +18359,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
additionalProperties: {},
},
},
voiceId: {
type: "string",
},
voiceAliases: {
type: "object",
propertyNames: {
type: "string",
},
additionalProperties: {
type: "string",
},
},
modelId: {
type: "string",
},
outputFormat: {
type: "string",
},
apiKey: {
anyOf: [
{
type: "string",
},
{
oneOf: [
{
type: "object",
properties: {
source: {
type: "string",
const: "env",
},
provider: {
type: "string",
pattern: "^[a-z][a-z0-9_-]{0,63}$",
},
id: {
type: "string",
pattern: "^[A-Z][A-Z0-9_]{0,127}$",
},
},
required: ["source", "provider", "id"],
additionalProperties: false,
},
{
type: "object",
properties: {
source: {
type: "string",
const: "file",
},
provider: {
type: "string",
pattern: "^[a-z][a-z0-9_-]{0,63}$",
},
id: {
type: "string",
},
},
required: ["source", "provider", "id"],
additionalProperties: false,
},
{
type: "object",
properties: {
source: {
type: "string",
const: "exec",
},
provider: {
type: "string",
pattern: "^[a-z][a-z0-9_-]{0,63}$",
},
id: {
type: "string",
},
},
required: ["source", "provider", "id"],
additionalProperties: false,
},
],
},
],
},
interruptOnSpeech: {
type: "boolean",
},
@@ -23046,11 +22953,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
help: "Server-side request forgery guardrail settings for browser/network fetch paths that could reach internal hosts. Keep restrictive defaults in production and open only explicitly approved targets.",
tags: ["access"],
},
"browser.ssrfPolicy.allowPrivateNetwork": {
label: "Browser Allow Private Network",
help: "Legacy alias for browser.ssrfPolicy.dangerouslyAllowPrivateNetwork. Prefer the dangerously-named key so risk intent is explicit.",
tags: ["access"],
},
"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork": {
label: "Browser Dangerously Allow Private Network",
help: "Allows access to private-network address ranges from browser tooling. Default is enabled for trusted-network operator setups; disable to enforce strict public-only resolution checks.",
@@ -23770,26 +23672,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
help: "Enables automatic live-reload behavior for canvas assets during development workflows. Keep disabled in production-like environments where deterministic output is preferred.",
tags: ["reliability"],
},
"talk.voiceId": {
label: "Talk Voice ID",
help: "Legacy ElevenLabs default voice ID for Talk mode. Prefer talk.providers.elevenlabs.voiceId.",
tags: ["media"],
},
"talk.voiceAliases": {
label: "Talk Voice Aliases",
help: 'Use this legacy ElevenLabs voice alias map (for example {"Clawd":"EXAVITQu4vr4xnSDxMaL"}) only during migration. Prefer talk.providers.elevenlabs.voiceAliases.',
tags: ["media"],
},
"talk.modelId": {
label: "Talk Model ID",
help: "Legacy ElevenLabs model ID for Talk mode (default: eleven_v3). Prefer talk.providers.elevenlabs.modelId.",
tags: ["models", "media"],
},
"talk.outputFormat": {
label: "Talk Output Format",
help: "Use this legacy ElevenLabs output format for Talk mode (for example pcm_44100 or mp3_44100_128) only during migration. Prefer talk.providers.elevenlabs.outputFormat.",
tags: ["media"],
},
"talk.interruptOnSpeech": {
label: "Talk Interrupt on Speech",
help: "If true (default), stop assistant speech when the user starts speaking in Talk mode. Keep enabled for conversational turn-taking.",
@@ -23892,7 +23774,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
},
"messages.statusReactions.enabled": {
label: "Enable Status Reactions",
help: "Enable lifecycle status reactions for Telegram. When enabled, the ack reaction becomes the initial 'queued' state and progresses through thinking, tool, done/error automatically. Default: false.",
help: "Enable lifecycle status reactions on supported channels. Slack and Discord treat unset as enabled when ack reactions are active; Telegram requires this to be true before lifecycle reactions are used.",
tags: ["advanced"],
},
"messages.statusReactions.emojis": {
@@ -23972,12 +23854,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA = {
tags: ["security", "auth", "media"],
sensitive: true,
},
"talk.apiKey": {
label: "Talk API Key",
help: "Use this legacy ElevenLabs API key for Talk mode only during migration, and keep secrets in env-backed storage. Prefer talk.providers.elevenlabs.apiKey (fallback: ELEVENLABS_API_KEY).",
tags: ["security", "auth", "media"],
sensitive: true,
},
"channels.defaults": {
label: "Channel Defaults",
help: "Default channel behavior applied across providers when provider-specific settings are not set. Use this to enforce consistent baseline policy before per-provider tuning.",

View File

@@ -315,10 +315,6 @@ const TARGET_KEYS = [
"canvasHost.port",
"canvasHost.liveReload",
"talk",
"talk.voiceId",
"talk.voiceAliases",
"talk.modelId",
"talk.outputFormat",
"talk.interruptOnSpeech",
"talk.silenceTimeoutMs",
"meta",
@@ -536,7 +532,6 @@ const FINAL_BACKLOG_TARGET_KEYS = [
"browser.snapshotDefaults",
"browser.snapshotDefaults.mode",
"browser.ssrfPolicy",
"browser.ssrfPolicy.allowPrivateNetwork",
"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork",
"browser.ssrfPolicy.allowedHostnames",
"browser.ssrfPolicy.hostnameAllowlist",
@@ -554,7 +549,6 @@ const FINAL_BACKLOG_TARGET_KEYS = [
"gateway.remote.token",
"skills.load.watch",
"skills.load.watchDebounceMs",
"talk.apiKey",
"ui.assistant.avatar",
"ui.assistant.name",
"ui.seamColor",

View File

@@ -151,16 +151,6 @@ export const FIELD_HELP: Record<string, string> = {
"talk.providers.*.modelId": "Provider default model ID for Talk mode.",
"talk.providers.*.outputFormat": "Provider default output format for Talk mode.",
"talk.providers.*.apiKey": "Provider API key for Talk mode.", // pragma: allowlist secret
"talk.voiceId":
"Legacy ElevenLabs default voice ID for Talk mode. Prefer talk.providers.elevenlabs.voiceId.",
"talk.voiceAliases":
'Use this legacy ElevenLabs voice alias map (for example {"Clawd":"EXAVITQu4vr4xnSDxMaL"}) only during migration. Prefer talk.providers.elevenlabs.voiceAliases.',
"talk.modelId":
"Legacy ElevenLabs model ID for Talk mode (default: eleven_v3). Prefer talk.providers.elevenlabs.modelId.",
"talk.outputFormat":
"Use this legacy ElevenLabs output format for Talk mode (for example pcm_44100 or mp3_44100_128) only during migration. Prefer talk.providers.elevenlabs.outputFormat.",
"talk.apiKey":
"Use this legacy ElevenLabs API key for Talk mode only during migration, and keep secrets in env-backed storage. Prefer talk.providers.elevenlabs.apiKey (fallback: ELEVENLABS_API_KEY).",
"talk.interruptOnSpeech":
"If true (default), stop assistant speech when the user starts speaking in Talk mode. Keep enabled for conversational turn-taking.",
"talk.silenceTimeoutMs": `Milliseconds of user silence before Talk mode finalizes and sends the current transcript. Leave unset to keep the platform default pause window (${describeTalkSilenceTimeoutDefaults()}).`,
@@ -277,8 +267,6 @@ export const FIELD_HELP: Record<string, string> = {
"Default snapshot extraction mode controlling how page content is transformed for agent consumption. Choose the mode that balances readability, fidelity, and token footprint for your workflows.",
"browser.ssrfPolicy":
"Server-side request forgery guardrail settings for browser/network fetch paths that could reach internal hosts. Keep restrictive defaults in production and open only explicitly approved targets.",
"browser.ssrfPolicy.allowPrivateNetwork":
"Legacy alias for browser.ssrfPolicy.dangerouslyAllowPrivateNetwork. Prefer the dangerously-named key so risk intent is explicit.",
"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork":
"Allows access to private-network address ranges from browser tooling. Default is enabled for trusted-network operator setups; disable to enforce strict public-only resolution checks.",
"browser.ssrfPolicy.allowedHostnames":

View File

@@ -567,7 +567,6 @@ export const FIELD_LABELS: Record<string, string> = {
"browser.snapshotDefaults": "Browser Snapshot Defaults",
"browser.snapshotDefaults.mode": "Browser Snapshot Mode",
"browser.ssrfPolicy": "Browser SSRF Policy",
"browser.ssrfPolicy.allowPrivateNetwork": "Browser Allow Private Network",
"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork": "Browser Dangerously Allow Private Network",
"browser.ssrfPolicy.allowedHostnames": "Browser Allowed Hostnames",
"browser.ssrfPolicy.hostnameAllowlist": "Browser Hostname Allowlist",
@@ -716,10 +715,6 @@ export const FIELD_LABELS: Record<string, string> = {
"canvasHost.port": "Canvas Host Port",
"canvasHost.liveReload": "Canvas Host Live Reload",
talk: "Talk",
"talk.voiceId": "Talk Voice ID",
"talk.voiceAliases": "Talk Voice Aliases",
"talk.modelId": "Talk Model ID",
"talk.outputFormat": "Talk Output Format",
"talk.interruptOnSpeech": "Talk Interrupt on Speech",
"talk.silenceTimeoutMs": "Talk Silence Timeout (ms)",
messages: "Messages",
@@ -757,7 +752,6 @@ export const FIELD_LABELS: Record<string, string> = {
"talk.providers.*.modelId": "Talk Provider Model ID",
"talk.providers.*.outputFormat": "Talk Provider Output Format",
"talk.providers.*.apiKey": "Talk Provider API Key", // pragma: allowlist secret
"talk.apiKey": "Talk API Key", // pragma: allowlist secret
channels: "Channels",
"channels.defaults": "Channel Defaults",
"channels.defaults.groupPolicy": "Default Group Policy",

View File

@@ -45,11 +45,6 @@ describe("talk normalization", () => {
apiKey: "secret-key", // pragma: allowlist secret
},
},
voiceId: "voice-123",
voiceAliases: { Clawd: "EXAVITQu4vr4xnSDxMaL" },
modelId: "eleven_v3",
outputFormat: "pcm_44100",
apiKey: "secret-key", // pragma: allowlist secret
interruptOnSpeech: false,
silenceTimeoutMs: 1500,
});
@@ -76,7 +71,6 @@ describe("talk normalization", () => {
custom: true,
},
},
voiceId: "legacy-voice",
interruptOnSpeech: true,
});
});
@@ -109,8 +103,6 @@ describe("talk normalization", () => {
modelId: "acme-model",
},
},
voiceId: "acme-voice",
modelId: "acme-model",
interruptOnSpeech: true,
});
});
@@ -151,7 +143,6 @@ describe("talk normalization", () => {
expect(snapshot.config.talk?.provider).toBeUndefined();
expect(snapshot.config.talk?.providers?.elevenlabs?.voiceId).toBe("voice-123");
expect(snapshot.config.talk?.providers?.elevenlabs?.apiKey).toBe(elevenLabsApiKey);
expect(snapshot.config.talk?.apiKey).toBe(elevenLabsApiKey);
},
);
});
@@ -177,7 +168,6 @@ describe("talk normalization", () => {
expect(snapshot.config.talk?.provider).toBe("acme");
expect(snapshot.config.talk?.providers?.acme?.voiceId).toBe("acme-voice");
expect(snapshot.config.talk?.providers?.acme?.apiKey).toBeUndefined();
expect(snapshot.config.talk?.apiKey).toBeUndefined();
},
);
});
@@ -200,12 +190,11 @@ describe("talk normalization", () => {
async (configPath) => {
const io = createConfigIO({ configPath });
const snapshot = await io.readConfigFileSnapshot();
expect(snapshot.config.talk?.apiKey).toEqual({
expect(snapshot.config.talk?.providers?.elevenlabs?.apiKey).toEqual({
source: "env",
provider: "default",
id: "ELEVENLABS_API_KEY",
});
expect(snapshot.config.talk?.providers?.elevenlabs?.apiKey).toBeUndefined();
},
);
});

View File

@@ -115,35 +115,6 @@ function normalizeTalkProviders(value: unknown): Record<string, TalkProviderConf
return Object.keys(providers).length > 0 ? providers : undefined;
}
function normalizedLegacyTalkFields(source: Record<string, unknown>): Partial<TalkConfig> {
const legacy: Partial<TalkConfig> = {};
const voiceId = normalizeString(source.voiceId);
if (voiceId) {
legacy.voiceId = voiceId;
}
const voiceAliases = normalizeVoiceAliases(source.voiceAliases);
if (voiceAliases) {
legacy.voiceAliases = voiceAliases;
}
const modelId = normalizeString(source.modelId);
if (modelId) {
legacy.modelId = modelId;
}
const outputFormat = normalizeString(source.outputFormat);
if (outputFormat) {
legacy.outputFormat = outputFormat;
}
const apiKey = normalizeTalkSecretInput(source.apiKey);
if (apiKey !== undefined) {
legacy.apiKey = apiKey;
}
const silenceTimeoutMs = normalizeSilenceTimeoutMs(source.silenceTimeoutMs);
if (silenceTimeoutMs !== undefined) {
legacy.silenceTimeoutMs = silenceTimeoutMs;
}
return legacy;
}
function legacyProviderConfigFromTalk(
source: Record<string, unknown>,
): TalkProviderConfig | undefined {
@@ -169,38 +140,6 @@ function activeProviderFromTalk(talk: TalkConfig): string | undefined {
return providerIds.length === 1 ? providerIds[0] : undefined;
}
function legacyTalkFieldsFromProviderConfig(
config: TalkProviderConfig | undefined,
): Partial<TalkConfig> {
if (!config) {
return {};
}
const legacy: Partial<TalkConfig> = {};
if (typeof config.voiceId === "string") {
legacy.voiceId = config.voiceId;
}
if (
config.voiceAliases &&
typeof config.voiceAliases === "object" &&
!Array.isArray(config.voiceAliases)
) {
const aliases = normalizeVoiceAliases(config.voiceAliases);
if (aliases) {
legacy.voiceAliases = aliases;
}
}
if (typeof config.modelId === "string") {
legacy.modelId = config.modelId;
}
if (typeof config.outputFormat === "string") {
legacy.outputFormat = config.outputFormat;
}
if (config.apiKey !== undefined) {
legacy.apiKey = config.apiKey;
}
return legacy;
}
export function normalizeTalkSection(value: TalkConfig | undefined): TalkConfig | undefined {
if (!isPlainObject(value)) {
return undefined;
@@ -209,13 +148,13 @@ export function normalizeTalkSection(value: TalkConfig | undefined): TalkConfig
const source = value as Record<string, unknown>;
const hasNormalizedShape = typeof source.provider === "string" || isPlainObject(source.providers);
const normalized: TalkConfig = {};
const legacy = normalizedLegacyTalkFields(source);
if (Object.keys(legacy).length > 0) {
Object.assign(normalized, legacy);
}
if (typeof source.interruptOnSpeech === "boolean") {
normalized.interruptOnSpeech = source.interruptOnSpeech;
}
const silenceTimeoutMs = normalizeSilenceTimeoutMs(source.silenceTimeoutMs);
if (silenceTimeoutMs !== undefined) {
normalized.silenceTimeoutMs = silenceTimeoutMs;
}
if (hasNormalizedShape) {
const providers = normalizeTalkProviders(source.providers);
@@ -286,23 +225,16 @@ export function buildTalkConfigResponse(value: unknown): TalkConfigResponse | un
if (normalized.providers && Object.keys(normalized.providers).length > 0) {
payload.providers = normalized.providers;
}
if (typeof normalized.provider === "string") {
payload.provider = normalized.provider;
}
const resolved = resolveActiveTalkProviderConfig(normalized);
const activeProvider = normalizeString(normalized.provider) ?? resolved?.provider;
if (activeProvider) {
payload.provider = activeProvider;
}
if (resolved) {
payload.resolved = resolved;
}
const providerConfig = resolved?.config;
const providerCompatibilityLegacy = legacyTalkFieldsFromProviderConfig(providerConfig);
const compatibilityLegacy =
Object.keys(providerCompatibilityLegacy).length > 0
? providerCompatibilityLegacy
: normalizedLegacyTalkFields(normalized as unknown as Record<string, unknown>);
Object.assign(payload, compatibilityLegacy);
return Object.keys(payload).length > 0 ? payload : undefined;
}

View File

@@ -28,8 +28,6 @@ export type AgentSandboxConfig = {
sessionToolsVisibility?: "spawned" | "all";
/** Container/workspace scope for sandbox isolation. */
scope?: "session" | "agent" | "shared";
/** Legacy alias for scope ("session" when true, "shared" when false). */
perSession?: boolean;
workspaceRoot?: string;
/** Docker-specific sandbox settings. */
docker?: SandboxDockerSettings;

View File

@@ -17,8 +17,6 @@ export type BrowserSnapshotDefaults = {
mode?: "efficient";
};
export type BrowserSsrFPolicyConfig = {
/** Legacy alias for private-network access. Prefer dangerouslyAllowPrivateNetwork. */
allowPrivateNetwork?: boolean;
/** If true, permit browser navigation to private/internal networks. Default: true */
dangerouslyAllowPrivateNetwork?: boolean;
/**

View File

@@ -264,14 +264,8 @@ export type DiscordAccountConfig = {
* - "partial": edit a single preview message
* - "block": stream in chunked preview updates
* - "progress": alias that maps to "partial" on Discord
*
* Legacy boolean values are still accepted and auto-migrated.
*/
streaming?: DiscordStreamMode | boolean;
/**
* @deprecated Legacy key; migrated automatically to `streaming`.
*/
streamMode?: "partial" | "block" | "off";
streaming?: DiscordStreamMode;
/** Chunking config for Discord stream previews in `streaming: "block"`. */
draftChunk?: BlockStreamingChunkConfig;
/** Merge streamed block replies before sending. */

View File

@@ -79,16 +79,6 @@ export type TalkConfig = {
interruptOnSpeech?: boolean;
/** Milliseconds of user silence before Talk mode sends the transcript after a pause. */
silenceTimeoutMs?: number;
/**
* Legacy ElevenLabs compatibility fields.
* Kept during rollout while older clients migrate to provider/providers.
*/
voiceId?: string;
voiceAliases?: Record<string, string>;
modelId?: string;
outputFormat?: string;
apiKey?: SecretInput;
};
export type TalkConfigResponse = TalkConfig & {

View File

@@ -50,7 +50,6 @@ export type SlackChannelConfig = {
export type SlackReactionNotificationMode = "off" | "own" | "all" | "allowlist";
export type SlackStreamingMode = "off" | "partial" | "block" | "progress";
export type SlackLegacyStreamMode = "replace" | "status_final" | "append";
export type SlackExecApprovalTarget = "dm" | "channel" | "both";
export type SlackExecApprovalConfig = {
/** Enable mode for Slack exec approvals on this account. Default: auto when approvers can be resolved; false disables. */
@@ -163,17 +162,13 @@ export type SlackAccountConfig = {
* - "partial": replace preview text with the latest partial output (default)
* - "block": append chunked preview updates
* - "progress": show progress status, then send final text
*
* Legacy boolean values are still accepted and auto-migrated.
*/
streaming?: SlackStreamingMode | boolean;
streaming?: SlackStreamingMode;
/**
* Slack native text streaming toggle (`chat.startStream` / `chat.appendStream` / `chat.stopStream`).
* Used when `streaming` is `partial`. Default: true.
*/
nativeStreaming?: boolean;
/** @deprecated Legacy preview mode key; migrated automatically to `streaming`. */
streamMode?: SlackLegacyStreamMode;
mediaMaxMb?: number;
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
reactionNotifications?: SlackReactionNotificationMode;

View File

@@ -156,18 +156,14 @@ export type TelegramAccountConfig = {
* - "partial": edit a single preview message
* - "block": stream in larger chunked updates
* - "progress": alias that maps to "partial" on Telegram
*
* Legacy boolean values are still accepted and auto-migrated.
*/
streaming?: TelegramStreamingMode | boolean;
streaming?: TelegramStreamingMode;
/** Disable block streaming for this account. */
blockStreaming?: boolean;
/** @deprecated Legacy chunking config from `streamMode: "block"`; ignored after migration. */
draftChunk?: BlockStreamingChunkConfig;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
/** @deprecated Legacy key; migrated automatically to `streaming`. */
streamMode?: "off" | "partial" | "block";
mediaMaxMb?: number;
/** Telegram API client timeout in seconds (grammY ApiClientOptions). */
timeoutSeconds?: number;

View File

@@ -1,5 +1,3 @@
import type { SecretInput } from "./types.secrets.js";
export type TtsProvider = string;
export type TtsMode = "final" | "all";
@@ -27,65 +25,7 @@ export type TtsModelOverrideConfig = {
export type TtsProviderConfigMap = Record<string, Record<string, unknown>>;
export type LegacyTtsConfigCompat = {
/** Legacy ElevenLabs configuration. Prefer providers.elevenlabs. */
elevenlabs?: {
apiKey?: SecretInput;
baseUrl?: string;
voiceId?: string;
modelId?: string;
seed?: number;
applyTextNormalization?: "auto" | "on" | "off";
languageCode?: string;
voiceSettings?: {
stability?: number;
similarityBoost?: number;
style?: number;
useSpeakerBoost?: boolean;
speed?: number;
};
};
/** Legacy OpenAI configuration. Prefer providers.openai. */
openai?: {
apiKey?: SecretInput;
baseUrl?: string;
model?: string;
voice?: string;
/** Playback speed (0.254.0, default 1.0). */
speed?: number;
/** System-level instructions for the TTS model (gpt-4o-mini-tts only). */
instructions?: string;
};
/** Legacy alias for Microsoft speech configuration. Prefer providers.microsoft. */
edge?: {
/** Explicitly allow Microsoft speech usage (no API key required). */
enabled?: boolean;
voice?: string;
lang?: string;
outputFormat?: string;
pitch?: string;
rate?: string;
volume?: string;
saveSubtitles?: boolean;
proxy?: string;
timeoutMs?: number;
};
/** Legacy Microsoft speech configuration. Prefer providers.microsoft. */
microsoft?: {
enabled?: boolean;
voice?: string;
lang?: string;
outputFormat?: string;
pitch?: string;
rate?: string;
volume?: string;
saveSubtitles?: boolean;
proxy?: string;
timeoutMs?: number;
};
};
export type TtsConfig = LegacyTtsConfigCompat & {
export type TtsConfig = {
/** Auto-TTS mode (preferred). */
auto?: TtsAutoMode;
/** Legacy: enable auto-TTS when `auto` is not set. */

View File

@@ -529,7 +529,6 @@ export const AgentSandboxSchema = z
workspaceAccess: z.union([z.literal("none"), z.literal("ro"), z.literal("rw")]).optional(),
sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(),
scope: z.union([z.literal("session"), z.literal("agent"), z.literal("shared")]).optional(),
perSession: z.boolean().optional(),
workspaceRoot: z.string().optional(),
docker: SandboxDockerSchema,
ssh: SandboxSshSchema,

View File

@@ -1,12 +1,6 @@
import { z } from "zod";
import { isSafeScpRemoteHost } from "../infra/scp-host.js";
import { isValidInboundPathRootPattern } from "../media/inbound-path-policy.js";
import {
resolveDiscordPreviewStreamMode,
resolveSlackNativeStreaming,
resolveSlackStreamingMode,
resolveTelegramPreviewStreamMode,
} from "./discord-preview-streaming.js";
import {
normalizeTelegramCommandDescription,
normalizeTelegramCommandName,
@@ -177,26 +171,6 @@ const validateTelegramCustomCommands = (
}
};
function normalizeTelegramStreamingConfig(value: { streaming?: unknown; streamMode?: unknown }) {
value.streaming = resolveTelegramPreviewStreamMode(value);
delete value.streamMode;
}
function normalizeDiscordStreamingConfig(value: { streaming?: unknown; streamMode?: unknown }) {
value.streaming = resolveDiscordPreviewStreamMode(value);
delete value.streamMode;
}
function normalizeSlackStreamingConfig(value: {
streaming?: unknown;
nativeStreaming?: unknown;
streamMode?: unknown;
}) {
value.nativeStreaming = resolveSlackNativeStreaming(value);
value.streaming = resolveSlackStreamingMode(value);
delete value.streamMode;
}
export const TelegramAccountSchemaBase = z
.object({
name: z.string().optional(),
@@ -232,12 +206,10 @@ export const TelegramAccountSchemaBase = z
direct: z.record(z.string(), TelegramDirectSchema.optional()).optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
streaming: z.union([z.boolean(), z.enum(["off", "partial", "block", "progress"])]).optional(),
streaming: z.enum(["off", "partial", "block", "progress"]).optional(),
blockStreaming: z.boolean().optional(),
draftChunk: BlockStreamingChunkSchema.optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
// Legacy key kept for automatic migration to `streaming`.
streamMode: z.enum(["off", "partial", "block"]).optional(),
mediaMaxMb: z.number().positive().optional(),
timeoutSeconds: z.number().int().positive().optional(),
retry: RetryConfigSchema,
@@ -331,7 +303,6 @@ export const TelegramAccountSchemaBase = z
.strict();
export const TelegramAccountSchema = TelegramAccountSchemaBase.superRefine((value, ctx) => {
normalizeTelegramStreamingConfig(value);
// Account-level schemas skip allowFrom validation because accounts inherit
// allowFrom from the parent channel config at runtime (resolveTelegramAccount
// shallow-merges top-level and account values in src/telegram/accounts.ts).
@@ -343,7 +314,6 @@ export const TelegramConfigSchema = TelegramAccountSchemaBase.extend({
accounts: z.record(z.string(), TelegramAccountSchema.optional()).optional(),
defaultAccount: z.string().optional(),
}).superRefine((value, ctx) => {
normalizeTelegramStreamingConfig(value);
requireOpenAllowFrom({
policy: value.dmPolicy,
allowFrom: value.allowFrom,
@@ -527,9 +497,7 @@ export const DiscordAccountSchema = z
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
// Canonical streaming mode. Legacy aliases (`streamMode`, boolean `streaming`) are auto-mapped.
streaming: z.union([z.boolean(), z.enum(["off", "partial", "block", "progress"])]).optional(),
streamMode: z.enum(["partial", "block", "off"]).optional(),
streaming: z.enum(["off", "partial", "block", "progress"]).optional(),
draftChunk: BlockStreamingChunkSchema.optional(),
maxLinesPerMessage: z.number().int().positive().optional(),
mediaMaxMb: z.number().positive().optional(),
@@ -656,8 +624,6 @@ export const DiscordAccountSchema = z
})
.strict()
.superRefine((value, ctx) => {
normalizeDiscordStreamingConfig(value);
const activityText = typeof value.activity === "string" ? value.activity.trim() : "";
const hasActivity = Boolean(activityText);
const hasActivityType = value.activityType !== undefined;
@@ -929,9 +895,8 @@ export const SlackAccountSchema = z
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
streaming: z.union([z.boolean(), z.enum(["off", "partial", "block", "progress"])]).optional(),
streaming: z.enum(["off", "partial", "block", "progress"]).optional(),
nativeStreaming: z.boolean().optional(),
streamMode: z.enum(["replace", "status_final", "append"]).optional(),
mediaMaxMb: z.number().positive().optional(),
reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(),
reactionAllowlist: z.array(z.union([z.string(), z.number()])).optional(),
@@ -974,9 +939,7 @@ export const SlackAccountSchema = z
typingReaction: z.string().optional(),
})
.strict()
.superRefine((value) => {
normalizeSlackStreamingConfig(value);
.superRefine(() => {
// DM allowlist validation is enforced at SlackConfigSchema so account entries
// can inherit top-level allowFrom via runtime shallow merge.
});

View File

@@ -181,11 +181,6 @@ const TalkSchema = z
.object({
provider: z.string().optional(),
providers: z.record(z.string(), TalkProviderEntrySchema).optional(),
voiceId: z.string().optional(),
voiceAliases: z.record(z.string(), z.string()).optional(),
modelId: z.string().optional(),
outputFormat: z.string().optional(),
apiKey: SecretInputSchema.optional().register(sensitive),
interruptOnSpeech: z.boolean().optional(),
silenceTimeoutMs: z.number().int().positive().optional(),
})
@@ -375,7 +370,6 @@ export const OpenClawSchema = z
snapshotDefaults: BrowserSnapshotDefaultsSchema,
ssrfPolicy: z
.object({
allowPrivateNetwork: z.boolean().optional(),
dangerouslyAllowPrivateNetwork: z.boolean().optional(),
allowedHostnames: z.array(z.string()).optional(),
hostnameAllowlist: z.array(z.string()).optional(),

View File

@@ -36,7 +36,7 @@ const fixtures = JSON.parse(fs.readFileSync(fixturePath, "utf-8")) as TalkConfig
describe("talk.config contract fixtures", () => {
for (const fixture of fixtures.selectionCases) {
it(fixture.id, () => {
const payload = { config: { talk: fixture.talk } };
const payload = { config: { talk: buildTalkConfigResponse(fixture.talk) } };
if (fixture.payloadValid) {
expect(validateTalkConfigResult(payload)).toBe(true);
} else {
@@ -55,16 +55,12 @@ describe("talk.config contract fixtures", () => {
apiKey?: string;
};
};
voiceId?: string;
apiKey?: string;
};
expect(talk.resolved?.provider ?? fixture.defaultProvider).toBe(
fixture.expectedSelection.provider,
);
expect(talk.resolved?.config?.voiceId ?? talk.voiceId).toBe(
fixture.expectedSelection.voiceId,
);
expect(talk.resolved?.config?.apiKey ?? talk.apiKey).toBe(fixture.expectedSelection.apiKey);
expect(talk.resolved?.config?.voiceId).toBe(fixture.expectedSelection.voiceId);
expect(talk.resolved?.config?.apiKey).toBe(fixture.expectedSelection.apiKey);
});
}

View File

@@ -16,6 +16,7 @@ import {
connectOk,
installGatewayTestHooks,
readConnectChallengeNonce,
resetTestPluginRegistry,
rpcReq,
} from "./test-helpers.js";
import { withServer } from "./test-with-server.js";
@@ -35,8 +36,6 @@ type TalkConfigPayload = {
provider?: string;
config?: { voiceId?: string; apiKey?: string | SecretRef };
};
apiKey?: string | SecretRef;
voiceId?: string;
silenceTimeoutMs?: number;
};
session?: { mainKey?: string };
@@ -95,7 +94,20 @@ async function writeTalkConfig(config: {
silenceTimeoutMs?: number;
}) {
const { writeConfigFile } = await import("../config/config.js");
await writeConfigFile({ talk: config });
await writeConfigFile({
talk: {
silenceTimeoutMs: config.silenceTimeoutMs,
providers:
config.apiKey !== undefined || config.voiceId !== undefined
? {
elevenlabs: {
...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}),
...(config.voiceId !== undefined ? { voiceId: config.voiceId } : {}),
},
}
: undefined,
},
});
}
async function fetchTalkConfig(
@@ -105,8 +117,12 @@ async function fetchTalkConfig(
return rpcReq<TalkConfigPayload>(ws, "talk.config", params ?? {});
}
async function fetchTalkSpeak(ws: GatewaySocket, params: Record<string, unknown>) {
return rpcReq<TalkSpeakPayload>(ws, "talk.speak", params);
async function fetchTalkSpeak(
ws: GatewaySocket,
params: Record<string, unknown>,
timeoutMs?: number,
) {
return rpcReq<TalkSpeakPayload>(ws, "talk.speak", params, timeoutMs);
}
function expectElevenLabsTalkConfig(
@@ -121,12 +137,10 @@ function expectElevenLabsTalkConfig(
expect(talk?.providers?.elevenlabs?.voiceId).toBe(expected.voiceId);
expect(talk?.resolved?.provider).toBe("elevenlabs");
expect(talk?.resolved?.config?.voiceId).toBe(expected.voiceId);
expect(talk?.voiceId).toBe(expected.voiceId);
if ("apiKey" in expected) {
expect(talk?.providers?.elevenlabs?.apiKey).toEqual(expected.apiKey);
expect(talk?.resolved?.config?.apiKey).toEqual(expected.apiKey);
expect(talk?.apiKey).toEqual(expected.apiKey);
}
if ("silenceTimeoutMs" in expected) {
expect(talk?.silenceTimeoutMs).toBe(expected.silenceTimeoutMs);
@@ -138,8 +152,12 @@ describe("gateway talk.config", () => {
const { writeConfigFile } = await import("../config/config.js");
await writeConfigFile({
talk: {
voiceId: "voice-123",
apiKey: "secret-key-abc", // pragma: allowlist secret
providers: {
elevenlabs: {
voiceId: "voice-123",
apiKey: "secret-key-abc", // pragma: allowlist secret
},
},
silenceTimeoutMs: 1500,
},
session: {
@@ -227,7 +245,7 @@ describe("gateway talk.config", () => {
});
});
it("prefers normalized provider payload over conflicting legacy talk keys", async () => {
it("returns canonical provider talk payloads", async () => {
const { writeConfigFile } = await import("../config/config.js");
await writeConfigFile({
talk: {
@@ -237,7 +255,6 @@ describe("gateway talk.config", () => {
voiceId: "voice-normalized",
},
},
voiceId: "voice-legacy",
},
});
@@ -278,14 +295,19 @@ describe("gateway talk.config", () => {
try {
await withServer(async (ws) => {
resetTestPluginRegistry();
await connectOperator(ws, ["operator.read", "operator.write"]);
const res = await fetchTalkSpeak(ws, {
text: "Hello from talk mode.",
voiceId: "nova",
modelId: "tts-1",
speed: 1.25,
});
expect(res.ok).toBe(true);
const res = await fetchTalkSpeak(
ws,
{
text: "Hello from talk mode.",
voiceId: "nova",
modelId: "tts-1",
speed: 1.25,
},
30_000,
);
expect(res.ok, JSON.stringify(res)).toBe(true);
expect(res.payload?.provider).toBe("openai");
expect(res.payload?.outputFormat).toBe("mp3");
expect(res.payload?.mimeType).toBe("audio/mpeg");
@@ -332,13 +354,14 @@ describe("gateway talk.config", () => {
try {
await withServer(async (ws) => {
resetTestPluginRegistry();
await connectOperator(ws, ["operator.read", "operator.write"]);
const res = await fetchTalkSpeak(ws, {
text: "Hello from talk mode.",
voiceId: "clawd",
outputFormat: "pcm_44100",
});
expect(res.ok).toBe(true);
expect(res.ok, JSON.stringify(res)).toBe(true);
expect(res.payload?.provider).toBe("elevenlabs");
expect(res.payload?.outputFormat).toBe("pcm_44100");
expect(res.payload?.audioBase64).toBe(Buffer.from([4, 5, 6]).toString("base64"));
@@ -365,40 +388,39 @@ describe("gateway talk.config", () => {
},
});
const previousRegistry = getActivePluginRegistry() ?? createEmptyPluginRegistry();
setActivePluginRegistry({
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "acme-plugin",
source: "test",
provider: {
id: "acme",
label: "Acme Speech",
isConfigured: () => true,
synthesize: async () => ({
audioBuffer: Buffer.from([7, 8, 9]),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
}),
await withServer(async (ws) => {
const previousRegistry = getActivePluginRegistry() ?? createEmptyPluginRegistry();
setActivePluginRegistry({
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "acme-plugin",
source: "test",
provider: {
id: "acme",
label: "Acme Speech",
isConfigured: () => true,
synthesize: async () => ({
audioBuffer: Buffer.from([7, 8, 9]),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
}),
},
},
},
],
});
try {
await withServer(async (ws) => {
],
});
try {
await connectOperator(ws, ["operator.read", "operator.write"]);
const res = await fetchTalkSpeak(ws, {
text: "Hello from plugin talk mode.",
});
expect(res.ok).toBe(true);
expect(res.ok, JSON.stringify(res)).toBe(true);
expect(res.payload?.provider).toBe("acme");
expect(res.payload?.audioBase64).toBe(Buffer.from([7, 8, 9]).toString("base64"));
});
} finally {
setActivePluginRegistry(previousRegistry);
}
} finally {
setActivePluginRegistry(previousRegistry);
}
});
});
});

View File

@@ -7,7 +7,7 @@
"expectedSelection": {
"provider": "elevenlabs",
"normalizedPayload": true,
"voiceId": "voice-resolved",
"voiceId": "voice-normalized",
"apiKey": "xxxxx"
},
"talk": {
@@ -32,8 +32,12 @@
{
"id": "normalized_missing_resolved",
"defaultProvider": "elevenlabs",
"payloadValid": false,
"expectedSelection": null,
"payloadValid": true,
"expectedSelection": {
"provider": "elevenlabs",
"normalizedPayload": true,
"voiceId": "voice-normalized"
},
"talk": {
"provider": "elevenlabs",
"providers": {