diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6b560f460..83c55a178fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Docs: https://docs.openclaw.ai - Followups/Typing indicator: ensure followup turns mark dispatch idle on every exit path (including `NO_REPLY`, empty payloads, and agent errors) so typing keepalive cleanup always runs and channel typing indicators do not get stuck after queued/silent followups. (#26881) Thanks @codexGW. - Telegram/Preview cleanup: keep finalized text previews when a later assistant message is media-only (for example mixed text plus voice turns) by skipping finalized preview archival at assistant-message boundaries, preventing cleanup from deleting already-visible final text messages. (#27042) - Slack/Allowlist channels: match channel IDs case-insensitively during channel allowlist resolution so lowercase config keys (for example `c0abc12345`) correctly match Slack runtime IDs (`C0ABC12345`) under `groupPolicy: "allowlist"`, preventing silent channel-event drops. (#26878) Thanks @lbo728. +- Voice-call/TTS tools: hide the `tts` tool when the message provider is `voice`, preventing voice-call runs from selecting self-playback TTS and falling into silent no-output loops. (#27025) - Tests/Low-memory stability: disable Vitest `vmForks` by default on low-memory local hosts (`<64 GiB`), keep low-profile extension lane parallelism at 4 workers, and align cron isolated-agent tests with `setSessionRuntimeModel` usage to avoid deterministic suite failures. (#26324) Thanks @ngutman. - Slack/Inbound media fallback: deliver file-only messages even when Slack media downloads fail by adding a filename placeholder fallback, capping fallback names to the shared media-file limit, and normalizing empty filenames to `file` so attachment-only messages are not silently dropped. (#25181) Thanks @justinhuangcode. diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts index 22d68f15ff8..4f2b0be9f47 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts @@ -319,6 +319,11 @@ describe("createOpenClawCodingTools", () => { expect(names.has("telegram")).toBe(false); expect(names.has("whatsapp")).toBe(false); }); + it("does not expose tts tool for voice message provider", () => { + const tools = createOpenClawCodingTools({ messageProvider: "voice" }); + const names = new Set(tools.map((tool) => tool.name)); + expect(names.has("tts")).toBe(false); + }); it("filters session tools for sub-agent sessions by default", () => { const tools = createOpenClawCodingTools({ sessionKey: "agent:main:subagent:test", diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index e2d29d375da..f4252f562bb 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -217,6 +217,8 @@ export function createOpenClawCodingTools(options?: { /** Whether the sender is an owner (required for owner-only tools). */ senderIsOwner?: boolean; }): AnyAgentTool[] { + const rawMessageProvider = options?.messageProvider?.trim().toLowerCase(); + const isVoiceMessageProvider = rawMessageProvider === "voice"; const execToolName = "exec"; const sandbox = options?.sandbox?.enabled ? options.sandbox : undefined; const { @@ -480,9 +482,12 @@ export function createOpenClawCodingTools(options?: { senderIsOwner: options?.senderIsOwner, }), ]; + const toolsForMessageProvider = isVoiceMessageProvider + ? tools.filter((tool) => tool.name !== "tts") + : tools; // Security: treat unknown/undefined as unauthorized (opt-in, not opt-out) const senderIsOwner = options?.senderIsOwner === true; - const toolsByAuthorization = applyOwnerOnlyToolPolicy(tools, senderIsOwner); + const toolsByAuthorization = applyOwnerOnlyToolPolicy(toolsForMessageProvider, senderIsOwner); const subagentFiltered = applyToolPolicyPipeline({ tools: toolsByAuthorization, toolMeta: (tool) => getPluginToolMeta(tool),