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.
|
- 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.
|
- TUI: add input history (up/down) for submitted messages. (#1348) — thanks @vignesh07.
|
||||||
### Fixes
|
### 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.
|
- 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: 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.
|
- 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 {
|
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.stopRecognition()
|
||||||
self.speechRecognizer = SFSpeechRecognizer()
|
self.speechRecognizer = SFSpeechRecognizer()
|
||||||
guard let recognizer = self.speechRecognizer else {
|
guard let recognizer = self.speechRecognizer else {
|
||||||
@ -146,6 +153,11 @@ final class TalkModeManager: NSObject {
|
|||||||
|
|
||||||
let input = self.audioEngine.inputNode
|
let input = self.audioEngine.inputNode
|
||||||
let format = input.outputFormat(forBus: 0)
|
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)
|
input.removeTap(onBus: 0)
|
||||||
let tapBlock = Self.makeAudioTapAppendCallback(request: request)
|
let tapBlock = Self.makeAudioTapAppendCallback(request: request)
|
||||||
input.installTap(onBus: 0, bufferSize: 2048, format: format, block: tapBlock)
|
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) {
|
export function registerDirectoryCli(program: Command) {
|
||||||
const directory = program
|
const directory = program
|
||||||
.command("directory")
|
.command("directory")
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { defaultRuntime } from "../runtime.js";
|
||||||
|
|
||||||
const { buildProgram } = await import("./program.js");
|
const { buildProgram } = await import("./program.js");
|
||||||
|
|
||||||
describe("dns cli", () => {
|
describe("dns cli", () => {
|
||||||
it("prints setup info (no apply)", async () => {
|
it("prints setup info (no apply)", async () => {
|
||||||
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {});
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
await program.parseAsync(["dns", "setup"], { from: "user" });
|
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 {
|
function parseSinceMs(raw: unknown, label: string): number | undefined {
|
||||||
if (raw === undefined || raw === null) return 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;
|
if (!value) return undefined;
|
||||||
try {
|
try {
|
||||||
return parseDurationMs(value);
|
return parseDurationMs(value);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { defaultRuntime } from "../runtime.js";
|
||||||
|
|
||||||
const listChannelPairingRequests = vi.fn();
|
const listChannelPairingRequests = vi.fn();
|
||||||
const approveChannelPairingCode = vi.fn();
|
const approveChannelPairingCode = vi.fn();
|
||||||
const notifyPairingApproved = 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();
|
const program = new Command();
|
||||||
program.name("test");
|
program.name("test");
|
||||||
registerPairingCli(program);
|
registerPairingCli(program);
|
||||||
await program.parseAsync(["pairing", "list", "--channel", "telegram"], {
|
await program.parseAsync(["pairing", "list", "--channel", "telegram"], {
|
||||||
from: "user",
|
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 () => {
|
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();
|
const program = new Command();
|
||||||
program.name("test");
|
program.name("test");
|
||||||
registerPairingCli(program);
|
registerPairingCli(program);
|
||||||
await program.parseAsync(["pairing", "list", "--channel", "discord"], {
|
await program.parseAsync(["pairing", "list", "--channel", "discord"], {
|
||||||
from: "user",
|
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 () => {
|
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();
|
const program = new Command();
|
||||||
program.name("test");
|
program.name("test");
|
||||||
registerPairingCli(program);
|
registerPairingCli(program);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ type CommandOptions = Record<string, unknown>;
|
|||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
|
|
||||||
const SANDBOX_EXAMPLES = {
|
const SANDBOX_EXAMPLES: Record<string, [string, string][]> = {
|
||||||
main: [
|
main: [
|
||||||
["clawdbot sandbox list", "List all sandbox containers."],
|
["clawdbot sandbox list", "List all sandbox containers."],
|
||||||
["clawdbot sandbox list --browser", "List only browser 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 --agent work", "Explain an agent sandbox."],
|
||||||
["clawdbot sandbox explain --json", "JSON output."],
|
["clawdbot sandbox explain --json", "JSON output."],
|
||||||
],
|
],
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
function createRunner(
|
function createRunner(
|
||||||
commandFn: (opts: CommandOptions, runtime: typeof defaultRuntime) => Promise<void>,
|
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
|
// 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
|
// enough to avoid flaky RPC timeouts, but still fail fast when a response
|
||||||
// never arrives.
|
// never arrives.
|
||||||
timeoutMs = 10_000,
|
timeoutMs = 15_000,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
const timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
const timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user