From b02c47043d17999a3cb0e00c9f74b092d7e53f1e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 21 Jan 2026 05:41:40 +0000 Subject: [PATCH] fix: clarify talk mode simulator limits (#1358) (thanks @vignesh07) --- CHANGELOG.md | 1 + apps/ios/Sources/Voice/TalkModeManager.swift | 3 ++- src/cli/directory-cli.ts | 7 +++++++ src/cli/dns-cli.test.ts | 7 +++++-- src/cli/nodes-cli/register.status.ts | 11 ++++++++++- src/cli/pairing-cli.test.ts | 16 +++++++++++----- src/cli/sandbox-cli.ts | 4 ++-- src/gateway/test-helpers.server.ts | 2 +- 8 files changed, 39 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5058a6ce3..c247f2e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Docs: https://docs.clawd.bot - UI: add copy-as-markdown with error feedback and drop legacy list view. (#1345) — thanks @bradleypriest. - TUI: add input history (up/down) for submitted messages. (#1348) — thanks @vignesh07. ### Fixes +- iOS: explain Talk mode is unavailable on the simulator to avoid Speech live-audio crashes. (#1358) — thanks @vignesh07. - Discovery: shorten Bonjour DNS-SD service type to `_clawdbot-gw._tcp` and update discovery clients/docs. - Agents: preserve subagent announce thread/topic routing + queued replies across channels. (#1241) — thanks @gnarco. - Agents: avoid treating timeout errors with "aborted" messages as user aborts, so model fallback still runs. diff --git a/apps/ios/Sources/Voice/TalkModeManager.swift b/apps/ios/Sources/Voice/TalkModeManager.swift index 16ae245eb..0ccc19dba 100644 --- a/apps/ios/Sources/Voice/TalkModeManager.swift +++ b/apps/ios/Sources/Voice/TalkModeManager.swift @@ -133,8 +133,9 @@ final class TalkModeManager: NSObject { private func startRecognition() throws { #if targetEnvironment(simulator) + // Apple Speech live-audio recognition is not supported on Simulator. throw NSError(domain: "TalkMode", code: 2, userInfo: [ - NSLocalizedDescriptionKey: "Talk mode is not supported on the iOS simulator", + NSLocalizedDescriptionKey: "Talk mode is not supported on the iOS simulator (Speech live audio requires a device).", ]) #endif diff --git a/src/cli/directory-cli.ts b/src/cli/directory-cli.ts index 347695f63..e2da2f14f 100644 --- a/src/cli/directory-cli.ts +++ b/src/cli/directory-cli.ts @@ -30,6 +30,13 @@ function buildRows(entries: Array<{ id: string; name?: string | undefined }>) { })); } +function formatEntry(entry: { id: string; name?: string; handle?: string }) { + const name = entry.name?.trim() ?? ""; + const handle = entry.handle?.trim() ?? ""; + const label = name || handle; + return label ? `${entry.id} - ${label}` : entry.id; +} + export function registerDirectoryCli(program: Command) { const directory = program .command("directory") diff --git a/src/cli/dns-cli.test.ts b/src/cli/dns-cli.test.ts index 6ad0938c6..90c1fec10 100644 --- a/src/cli/dns-cli.test.ts +++ b/src/cli/dns-cli.test.ts @@ -1,12 +1,15 @@ import { describe, expect, it, vi } from "vitest"; +import { defaultRuntime } from "../runtime.js"; + const { buildProgram } = await import("./program.js"); describe("dns cli", () => { it("prints setup info (no apply)", async () => { - const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); const program = buildProgram(); await program.parseAsync(["dns", "setup"], { from: "user" }); - expect(log).toHaveBeenCalledWith(expect.stringContaining("Domain:")); + const output = log.mock.calls.map((args) => args.join(" ")).join("\n"); + expect(output).toContain("clawdbot.internal"); }); }); diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 4f8e2ae76..2e9edc0e7 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -46,7 +46,16 @@ function formatNodeVersions(node: { function parseSinceMs(raw: unknown, label: string): number | undefined { if (raw === undefined || raw === null) return undefined; - const value = String(raw).trim(); + let value = ""; + if (typeof raw === "string") { + value = raw.trim(); + } else if (typeof raw === "number") { + value = `${raw}`; + } else { + defaultRuntime.error(`${label}: expected a duration string`); + defaultRuntime.exit(1); + return undefined; + } if (!value) return undefined; try { return parseDurationMs(value); diff --git a/src/cli/pairing-cli.test.ts b/src/cli/pairing-cli.test.ts index 9bbe0e3f2..6e18ec960 100644 --- a/src/cli/pairing-cli.test.ts +++ b/src/cli/pairing-cli.test.ts @@ -1,6 +1,8 @@ import { Command } from "commander"; import { describe, expect, it, vi } from "vitest"; +import { defaultRuntime } from "../runtime.js"; + const listChannelPairingRequests = vi.fn(); const approveChannelPairingCode = vi.fn(); const notifyPairingApproved = vi.fn(); @@ -64,14 +66,16 @@ describe("pairing cli", () => { }, ]); - const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); const program = new Command(); program.name("test"); registerPairingCli(program); await program.parseAsync(["pairing", "list", "--channel", "telegram"], { from: "user", }); - expect(log).toHaveBeenCalledWith(expect.stringContaining("telegramUserId=123")); + const output = log.mock.calls.map((args) => args.join(" ")).join("\n"); + expect(output).toContain("telegramUserId"); + expect(output).toContain("123"); }); it("accepts channel as positional for list", async () => { @@ -124,14 +128,16 @@ describe("pairing cli", () => { }, ]); - const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); const program = new Command(); program.name("test"); registerPairingCli(program); await program.parseAsync(["pairing", "list", "--channel", "discord"], { from: "user", }); - expect(log).toHaveBeenCalledWith(expect.stringContaining("discordUserId=999")); + const output = log.mock.calls.map((args) => args.join(" ")).join("\n"); + expect(output).toContain("discordUserId"); + expect(output).toContain("999"); }); it("accepts channel as positional for approve (npm-run compatible)", async () => { @@ -146,7 +152,7 @@ describe("pairing cli", () => { }, }); - const log = vi.spyOn(console, "log").mockImplementation(() => {}); + const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); const program = new Command(); program.name("test"); registerPairingCli(program); diff --git a/src/cli/sandbox-cli.ts b/src/cli/sandbox-cli.ts index 746566f82..51420300e 100644 --- a/src/cli/sandbox-cli.ts +++ b/src/cli/sandbox-cli.ts @@ -13,7 +13,7 @@ type CommandOptions = Record; // --- Helpers --- -const SANDBOX_EXAMPLES = { +const SANDBOX_EXAMPLES: Record = { main: [ ["clawdbot sandbox list", "List all sandbox containers."], ["clawdbot sandbox list --browser", "List only browser containers."], @@ -40,7 +40,7 @@ const SANDBOX_EXAMPLES = { ["clawdbot sandbox explain --agent work", "Explain an agent sandbox."], ["clawdbot sandbox explain --json", "JSON output."], ], -} as const; +}; function createRunner( commandFn: (opts: CommandOptions, runtime: typeof defaultRuntime) => Promise, diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index e3668815f..10f2f513a 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -187,7 +187,7 @@ export function onceMessage( // Full-suite runs can saturate the event loop (581+ files). Keep this high // enough to avoid flaky RPC timeouts, but still fail fast when a response // never arrives. - timeoutMs = 10_000, + timeoutMs = 15_000, ): Promise { return new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);