Compare commits
2 Commits
main
...
fix/ios-ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b02c47043d | ||
|
|
6f42c623b9 |
@ -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.
|
||||
|
||||
@ -132,6 +132,13 @@ 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 (Speech live audio requires a device).",
|
||||
])
|
||||
#endif
|
||||
|
||||
self.stopRecognition()
|
||||
self.speechRecognizer = SFSpeechRecognizer()
|
||||
guard let recognizer = self.speechRecognizer else {
|
||||
@ -146,6 +153,11 @@ final class TalkModeManager: NSObject {
|
||||
|
||||
let input = self.audioEngine.inputNode
|
||||
let format = input.outputFormat(forBus: 0)
|
||||
guard format.sampleRate > 0, format.channelCount > 0 else {
|
||||
throw NSError(domain: "TalkMode", code: 3, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Invalid audio input format",
|
||||
])
|
||||
}
|
||||
input.removeTap(onBus: 0)
|
||||
let tapBlock = Self.makeAudioTapAppendCallback(request: request)
|
||||
input.installTap(onBus: 0, bufferSize: 2048, format: format, block: tapBlock)
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -13,7 +13,7 @@ type CommandOptions = Record<string, unknown>;
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
const SANDBOX_EXAMPLES = {
|
||||
const SANDBOX_EXAMPLES: Record<string, [string, string][]> = {
|
||||
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<void>,
|
||||
|
||||
@ -187,7 +187,7 @@ export function onceMessage<T = unknown>(
|
||||
// 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<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user