+
+

+
+
Your Bestie
+
+ {state.community
+ ? new URL(state.community).host
+ : "Choose a community"}
+
+
+
:
+ }
+ variant={busy ? "tint" : "solid"}
+ shape="round"
+ aria-label={
+ busy ? "End Bestie conversation" : "Start Bestie voice conversation"
+ }
+ disabled={
+ !available || !state.community || state.phase === "stopping"
+ }
+ onClick={() => {
+ if (busy) void call.end();
+ else void call.start();
+ }}
+ />
+
+ {!available ? (
+
+ Voice is available in the live development app when
+ BUZZ_REALTIME_ENDPOINT is configured.
+
+ ) : (
+ <>
+
+ {state.message}
+
+
+
+ {connected && (
+
+ ) : (
+
+ )
+ }
+ aria-label={
+ state.muted
+ ? "Unmute Bestie microphone"
+ : "Mute Bestie microphone"
+ }
+ aria-pressed={state.muted}
+ onClick={call.mute}
+ />
+ )}
+
+
+
+ Bestie uses its own identity in this community. Closing this panel
+ ends the call.
+
+ >
+ )}
+
+ {state.messages.map((message) => (
+
+
+ {message.role === "user" ? "You" : "Bestie"}
+
+
+ {message.text}
+
+
+ ))}
+
+ {permission && (
+
+
+ {permission.title || "Approve tool call?"}
+
+
+ {JSON.stringify(permission.rawInput ?? permission, null, 2)}
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/bundled/bestie/call.test.ts b/src/bundled/bestie/call.test.ts
new file mode 100644
index 00000000..4101b295
--- /dev/null
+++ b/src/bundled/bestie/call.test.ts
@@ -0,0 +1,237 @@
+import { afterEach, expect, test, vi } from "vitest";
+import { createBestieCall } from "./call";
+import type { RelayData, RelaySnapshot } from "../../features/relay/service";
+import type { Voice, VoiceUI, openVoice } from "./media/voice.mjs";
+
+const disposals: (() => void)[] = [];
+afterEach(() => {
+ for (const dispose of disposals.splice(0)) dispose();
+});
+function fixture(customOpen?: typeof openVoice) {
+ const viewer = "ab".repeat(32);
+ let snapshot: RelaySnapshot = {
+ status: "ready",
+ generation: 1,
+ scope: `https://one.example:${viewer}`,
+ viewer,
+ session: {} as RelaySnapshot["session"],
+ };
+ const listeners = new Set<() => void>();
+ const relay: RelayData = {
+ snapshot: () => snapshot,
+ subscribe(fn) {
+ listeners.add(fn);
+ return () => {
+ listeners.delete(fn);
+ };
+ },
+ retry() {},
+ disconnect() {},
+ async clearCache() {},
+ };
+ let ui: VoiceUI | undefined,
+ options: Parameters