import { test } from "bun:test"; import assert from "node:assert/strict"; import { VoiceSessionManager } from "./voiceSessionManager.ts"; import { normalizeVoiceText } from "./voiceSessionHelpers.ts"; import { resolveVoiceRealtimeToolDescriptors } from "./voiceToolCallToolRegistry.ts"; import { createTestSettings } from "../testSettings.ts";
function createManager({ memory = null }: { memory?: unknown } = {}) { const logs: unknown[] = []; const recordedMessages: Array<Record<string, unknown>> = [];
const client = { on() {}, off() {}, guilds: { cache: new Map() }, users: { cache: new Map() }, user: { id: "bot-user", username: "clanky" } };
const manager = new VoiceSessionManager({ client, store: { logAction(entry: unknown) { logs.push(entry); }, recordMessage(entry: Record<string, unknown>) { recordedMessages.push(entry); }, getSettings() { return createTestSettings({ identity: { botName: "clanky" }, memory: { enabled: false }, agentStack: { runtimeConfig: { research: { enabled: false } } } }); } }, appConfig: {}, llm: { async generate() { return { text: "NO" }; } }, memory });
return { manager, logs, recordedMessages }; }
// --- Fix 1: Durable memory write tool gated by settings.memory.enabled ---
test("voice durable memory write tool is excluded when settings.memory.enabled is false", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ memory: { enabled: false }, agentStack: { runtimeConfig: { research: { enabled: true } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(!names.includes("memory_write"), "memory_write should be excluded"); });
test("voice durable memory write tool is included when settings.memory.enabled is true", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ memory: { enabled: true }, agentStack: { runtimeConfig: { research: { enabled: true } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(names.includes("memory_write"), "memory_write should be included"); });
test("voice durable memory write stays disabled when durable memory is disabled", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ memory: { enabled: false }, agentStack: { runtimeConfig: { research: { enabled: true } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(!names.includes("memory_write"), "memory_write should be excluded"); });
test("voice durable memory write remains available when memory is enabled", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ memory: { enabled: true }, agentStack: { runtimeConfig: { research: { enabled: true } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(names.includes("memory_write"), "memory_write should be included"); });
test("voice durable memory write falls back to canonical defaults when settings is null", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: null });
const names = tools.map((t: { name: string }) => t.name); assert.ok(names.includes("memory_write"), "memory_write should be included"); });
// --- Fix 1 regression: web_search still gated ---
test("web_search excluded when settings.webSearch.enabled is false", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ memory: { enabled: true }, agentStack: { runtimeConfig: { research: { enabled: false } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(!names.includes("web_search"), "web_search should be excluded"); assert.ok(names.includes("memory_write"), "memory_write should still be included"); });
test("web_search included when settings.webSearch.enabled is true", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ memory: { enabled: false }, agentStack: { runtimeConfig: { research: { enabled: true } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(names.includes("web_search"), "web_search should be included"); });
test("browser_browse stays available for hosted computer use when OpenAI OAuth is configured", () => { const { manager } = createManager(); manager.browserManager = {} as never; manager.llm = { getComputerUseClient() { return { client: { post() {} }, provider: "openai-oauth" }; } } as never;
const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ agentStack: { overrides: { browserRuntime: "openai_computer_use" }, runtimeConfig: { browser: { enabled: true, openaiComputerUse: { client: "auto" } } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(names.includes("browser_browse"), "browser_browse should be included"); });
test("swarm code tools are not exposed as realtime-local voice tools", () => { const { manager } = createManager(); const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: createTestSettings({ agentStack: { runtimeConfig: { devTeam: { claudeCode: { enabled: true } } } } }) });
const names = tools.map((t: { name: string }) => t.name); assert.ok(!names.includes("spawn_code_worker"), "spawn_code_worker should stay on the reply tool surface"); assert.ok(!names.includes("request_task"), "swarm task tools should stay on the reply tool surface"); });
test("voice minecraft_task tool is included when Minecraft is enabled and session hooks are available", () => { const { manager } = createManager(); manager.createMinecraftSession = async () => null; manager.subAgentSessions = { get() { return null; }, register() {}, list() { return []; }, remove() { return false; } };
const tools = resolveVoiceRealtimeToolDescriptors(manager, { session: null, settings: { memory: { enabled: false }, agentStack: { runtimeConfig: { minecraft: { enabled: true } } } } });
const names = tools.map((t: { name: string }) => t.name); assert.ok(names.includes("minecraft_task"), "minecraft_task should be included"); });
// --- Fix 2: Short transcript passes through normalizeVoiceText ---
test("normalizeVoiceText passes through short text", () => { assert.equal(normalizeVoiceText("hi", 1200), "hi"); });
test("normalizeVoiceText passes through single word", () => { assert.equal(normalizeVoiceText("yes", 1200), "yes"); });
test("normalizeVoiceText truncates text exceeding maxChars", () => { const long = "a".repeat(2000); const result = normalizeVoiceText(long, 100); assert.equal(result.length, 100); });
test("assistant voice turns are persisted into searchable message history", () => { const { manager, recordedMessages } = createManager(); const session = { id: "session-1", guildId: "guild-1", textChannelId: "text-1", ending: false, settingsSnapshot: createTestSettings({ identity: { botName: "clanky" } }), recentVoiceTurns: [], transcriptTurns: [] };
manager.recordVoiceTurn(session, { role: "assistant", text: "nvda was around 181 earlier" });
assert.equal(recordedMessages.length, 1); assert.equal(recordedMessages[0]?.isBot, true); assert.equal(recordedMessages[0]?.channelId, "text-1"); assert.equal(recordedMessages[0]?.content, "nvda was around 181 earlier"); });
// --- Fix 3: Pending ingestion awaited before memory slice ---
test("pending ingestion clears itself after completion", async () => { const events: string[] = [];
let resolveIngest: () => void; const ingestPromise = new Promise((resolve) => { resolveIngest = resolve; });
const mockMemory = { async ingestMessage() { await ingestPromise; events.push("ingest_done"); } };
const { manager } = createManager({ memory: mockMemory }); manager.getSessionFactProfileSlice = () => { events.push("lookup_done"); return { userFacts: [], relevantFacts: [], relevantMessages: [] }; };
const session = { id: "session-1", guildId: "guild-1", textChannelId: "text-1", pendingMemoryIngest: null as Promise | null };
// Queue ingestion — stores promise on session manager.queueVoiceMemoryIngest({ session, settings: createTestSettings({ memory: { enabled: true } }), userId: "user-1", transcript: "remember this fact about me" });
assert.ok(session.pendingMemoryIngest, "pendingMemoryIngest should be set on session");
// Now resolve the ingest (simulating async completion) resolveIngest!();
await session.pendingMemoryIngest;
assert.equal(session.pendingMemoryIngest, null, "pendingMemoryIngest should be cleared after completion"); });
