From fdc003bb3fe7b08dd230ef28c12461a334b197d3 Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:44:00 -0300 Subject: [PATCH 1/8] fix(tts): fail fast when no delivery channel is bound Prevents TTS tool from hanging indefinitely in isolated cron sessions where no channel is bound for audio delivery. Part of #43483 --- src/agents/tools/tts-tool.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/agents/tools/tts-tool.ts b/src/agents/tools/tts-tool.ts index 03ed3cd9a04..7b8150c5230 100644 --- a/src/agents/tools/tts-tool.ts +++ b/src/agents/tools/tts-tool.ts @@ -27,11 +27,25 @@ export function createTtsTool(opts?: { const params = args as Record; const text = readStringParam(params, "text", { required: true }); const channel = readStringParam(params, "channel"); + const resolvedChannel = channel ?? opts?.agentChannel; const cfg = opts?.config ?? loadConfig(); + + if (!resolvedChannel) { + return { + content: [ + { + type: "text", + text: "TTS requires a bound channel for audio delivery. Use sessionTarget: \"main\" or provide a channel parameter.", + }, + ], + details: { error: "no_channel" }, + }; + } + const result = await textToSpeech({ text, cfg, - channel: channel ?? opts?.agentChannel, + channel: resolvedChannel, }); if (result.success && result.audioPath) { From 014fc66da14634715fd5a8cccbbb9513fab5df10 Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:47:37 -0300 Subject: [PATCH 2/8] test(tts): add test for missing channel fail-fast Covers the new no_channel early return when TTS tool is called without a bound delivery channel. Part of #43483 --- src/agents/tools/tts-tool.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/agents/tools/tts-tool.test.ts b/src/agents/tools/tts-tool.test.ts index fe9a6c1def9..93b0083bcf6 100644 --- a/src/agents/tools/tts-tool.test.ts +++ b/src/agents/tools/tts-tool.test.ts @@ -9,8 +9,14 @@ const { createTtsTool } = await import("./tts-tool.js"); describe("createTtsTool", () => { it("uses SILENT_REPLY_TOKEN in guidance text", () => { const tool = createTtsTool(); - expect(tool.description).toContain("QUIET_TOKEN"); expect(tool.description).not.toContain("NO_REPLY"); }); + + it("returns error when no delivery channel is available", async () => { + const tool = createTtsTool(); + const result = await tool.execute("test-call", { text: "hello world" }); + expect(result.content[0].text).toContain("requires a bound channel"); + expect(result.details.error).toBe("no_channel"); + }); }); From f83a82169bf70c292b1e15888b89a1b3fdac666a Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:55:35 -0300 Subject: [PATCH 3/8] fix: move loadConfig after channel guard and fix string formatting --- src/agents/tools/tts-tool.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agents/tools/tts-tool.ts b/src/agents/tools/tts-tool.ts index 7b8150c5230..e7933cfa0cc 100644 --- a/src/agents/tools/tts-tool.ts +++ b/src/agents/tools/tts-tool.ts @@ -28,20 +28,20 @@ export function createTtsTool(opts?: { const text = readStringParam(params, "text", { required: true }); const channel = readStringParam(params, "channel"); const resolvedChannel = channel ?? opts?.agentChannel; - const cfg = opts?.config ?? loadConfig(); if (!resolvedChannel) { return { content: [ { type: "text", - text: "TTS requires a bound channel for audio delivery. Use sessionTarget: \"main\" or provide a channel parameter.", + text: 'TTS requires a bound channel for audio delivery. Use sessionTarget: "main" or provide a channel parameter.', }, ], details: { error: "no_channel" }, }; } + const cfg = opts?.config ?? loadConfig(); const result = await textToSpeech({ text, cfg, From 209ca50cb9723ccb62dddeea30bc217f8f2884d4 Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:59:09 -0300 Subject: [PATCH 4/8] fix: type assertions in tts-tool test --- src/agents/tools/tts-tool.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/agents/tools/tts-tool.test.ts b/src/agents/tools/tts-tool.test.ts index 93b0083bcf6..ec50fed03d8 100644 --- a/src/agents/tools/tts-tool.test.ts +++ b/src/agents/tools/tts-tool.test.ts @@ -16,7 +16,8 @@ describe("createTtsTool", () => { it("returns error when no delivery channel is available", async () => { const tool = createTtsTool(); const result = await tool.execute("test-call", { text: "hello world" }); - expect(result.content[0].text).toContain("requires a bound channel"); - expect(result.details.error).toBe("no_channel"); + const firstContent = result.content[0] as { type: string; text: string }; + expect(firstContent.text).toContain("requires a bound channel"); + expect((result.details as Record).error).toBe("no_channel"); }); }); From 134fdce48e35febc680b8a7901f29d06c24bd77a Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:38:06 -0300 Subject: [PATCH 5/8] fix: check agentChannel instead of resolvedChannel for delivery guard --- src/agents/tools/tts-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/tools/tts-tool.ts b/src/agents/tools/tts-tool.ts index e7933cfa0cc..349fdb0915b 100644 --- a/src/agents/tools/tts-tool.ts +++ b/src/agents/tools/tts-tool.ts @@ -29,7 +29,7 @@ export function createTtsTool(opts?: { const channel = readStringParam(params, "channel"); const resolvedChannel = channel ?? opts?.agentChannel; - if (!resolvedChannel) { + if (!opts?.agentChannel && !channel) { return { content: [ { From 646e1e41dc3ad2619ed14c5758a6328b76bcfe5c Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:48:20 -0300 Subject: [PATCH 6/8] fix: guard on agentChannel only, ignore args.channel for delivery check --- src/agents/tools/tts-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/tools/tts-tool.ts b/src/agents/tools/tts-tool.ts index 349fdb0915b..c77425e692a 100644 --- a/src/agents/tools/tts-tool.ts +++ b/src/agents/tools/tts-tool.ts @@ -29,7 +29,7 @@ export function createTtsTool(opts?: { const channel = readStringParam(params, "channel"); const resolvedChannel = channel ?? opts?.agentChannel; - if (!opts?.agentChannel && !channel) { + if (!opts?.agentChannel) { return { content: [ { From 5f96aab719d7116eeadd0aeefd635f68ae86d5d6 Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:49:15 -0300 Subject: [PATCH 7/8] test: cover channel arg bypass scenario --- src/agents/tools/tts-tool.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agents/tools/tts-tool.test.ts b/src/agents/tools/tts-tool.test.ts index ec50fed03d8..32418b7a5fa 100644 --- a/src/agents/tools/tts-tool.test.ts +++ b/src/agents/tools/tts-tool.test.ts @@ -13,11 +13,11 @@ describe("createTtsTool", () => { expect(tool.description).not.toContain("NO_REPLY"); }); - it("returns error when no delivery channel is available", async () => { + it("returns error even when channel arg is provided but no agentChannel", async () => { const tool = createTtsTool(); - const result = await tool.execute("test-call", { text: "hello world" }); + const result = await tool.execute("test-call", { text: "hello world", channel: "telegram" }); const firstContent = result.content[0] as { type: string; text: string }; - expect(firstContent.text).toContain("requires a bound channel"); + expect(firstContent.text).toContain("requires a bound"); expect((result.details as Record).error).toBe("no_channel"); }); }); From c54180de72c66119a5425d6188d7ba2471dca2bf Mon Sep 17 00:00:00 2001 From: Hiago Silva <97215740+Huntterxx@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:15:42 -0300 Subject: [PATCH 8/8] fix: update error message to match guard logic --- src/agents/tools/tts-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/tools/tts-tool.ts b/src/agents/tools/tts-tool.ts index c77425e692a..7951250afaf 100644 --- a/src/agents/tools/tts-tool.ts +++ b/src/agents/tools/tts-tool.ts @@ -34,7 +34,7 @@ export function createTtsTool(opts?: { content: [ { type: "text", - text: 'TTS requires a bound channel for audio delivery. Use sessionTarget: "main" or provide a channel parameter.', + text: 'TTS requires a bound session channel for audio delivery. Use sessionTarget: "main" instead of "isolated".', }, ], details: { error: "no_channel" },