import { test } from "bun:test"; import assert from "node:assert/strict"; import { SubAgentSessionManager } from "../agents/subAgentSession.ts"; import { buildReplyToolSet, executeReplyTool, type ReplyToolRuntime } from "./replyTools.ts"; import { buildVoiceRealtimeLocalToolSchemas } from "./toolRegistry.ts";
function createMockVoiceSession( overrides: Partial<NonNullable<ReplyToolRuntime["voiceSession"]>> = {} ): NonNullable<ReplyToolRuntime["voiceSession"]> { return { async musicSearch() { throw new Error("not used"); }, async musicPlay() { throw new Error("not used"); }, async videoSearch() { throw new Error("not used"); }, async videoPlay() { throw new Error("not used"); }, async musicQueueAdd() { throw new Error("not used"); }, async musicQueueNext() { throw new Error("not used"); }, async musicStop() { throw new Error("not used"); }, async musicPause() { throw new Error("not used"); }, async musicResume() { throw new Error("not used"); }, async musicReplyHandoff() { throw new Error("not used"); }, async musicSkip() { throw new Error("not used"); }, async musicNowPlaying() { throw new Error("not used"); }, async playSoundboard() { throw new Error("not used"); }, async leaveVoiceChannel() { throw new Error("not used"); }, ...overrides }; }
function createMockMinecraftSession({ id, ownerUserId, onRunTurn }: { id: string; ownerUserId: string | null; onRunTurn?: (input: string) => Promise<{ text: string; costUsd?: number; isError?: boolean; errorMessage?: string; sessionCompleted?: boolean; usage?: { inputTokens: number; outputTokens: number; cacheWriteTokens: number; cacheReadTokens: number; }; }>; }) { return { id, type: "minecraft" as const, createdAt: Date.now(), ownerUserId, lastUsedAt: Date.now(), status: "idle" as "idle" | "running" | "completed" | "error" | "cancelled", async runTurn(input: string) { this.lastUsedAt = Date.now(); return await (onRunTurn?.(input) ?? Promise.resolve({ text: "ok", costUsd: 0, isError: false, errorMessage: "", usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 } })); }, cancel() { this.status = "cancelled"; }, close() { this.status = "cancelled"; } }; }
test("buildReplyToolSet includes browser_browse when browser agent is enabled and available", () => { const tools = buildReplyToolSet({ browser: { enabled: true }, webSearch: { enabled: false }, memory: { enabled: false } }, { browserBrowseAvailable: true, conversationSearchAvailable: false });
assert.equal(tools.some((tool) => tool.name === "browser_browse"), true); });
test("buildReplyToolSet excludes browser_browse when caller opts out", () => { const tools = buildReplyToolSet({ browser: { enabled: true }, webSearch: { enabled: false }, memory: { enabled: false } }, { browserBrowseAvailable: false, conversationSearchAvailable: false });
assert.equal(tools.some((tool) => tool.name === "browser_browse"), false); });
test("buildReplyToolSet includes web_scrape when web search is enabled", () => { const tools = buildReplyToolSet({ browser: { enabled: false }, webSearch: { enabled: true }, memory: { enabled: false } }, { conversationSearchAvailable: false });
assert.equal(tools.some((tool) => tool.name === "web_scrape"), true); });
test("buildReplyToolSet excludes web_scrape when caller opts out", () => { const tools = buildReplyToolSet({ browser: { enabled: false }, webSearch: { enabled: true }, memory: { enabled: false } }, { webScrapeAvailable: false, conversationSearchAvailable: false });
assert.equal(tools.some((tool) => tool.name === "web_scrape"), false); });
test("buildReplyToolSet excludes web_scrape when web search is unavailable for the turn", () => { const tools = buildReplyToolSet({ browser: { enabled: false }, webSearch: { enabled: true }, memory: { enabled: false } }, { webSearchAvailable: false, conversationSearchAvailable: false });
assert.equal(tools.some((tool) => tool.name === "web_scrape"), false); });
test("buildReplyToolSet includes memory tools and conversation search when memory is enabled", () => { const toolNames = buildReplyToolSet({ browser: { enabled: false }, webSearch: { enabled: false }, memory: { enabled: true } }).map((tool) => tool.name);
assert.equal(toolNames.includes("memory_search"), true); assert.equal(toolNames.includes("memory_write"), true); assert.equal(toolNames.includes("conversation_search"), true); });
test("buildReplyToolSet exposes swarm code tools when the caller is allowed", () => { const toolNames = buildReplyToolSet({ browser: { enabled: false }, webSearch: { enabled: false }, memory: { enabled: false }, permissions: { devTasks: { allowedUserIds: ["user-1"] } }, agentStack: { runtimeConfig: { devTeam: { codexCli: { enabled: true } } } } }, { swarmToolsAvailable: true }).map((tool) => tool.name);
assert.equal(toolNames.includes("spawn_code_worker"), true); assert.equal(toolNames.includes("request_task"), true); assert.equal(toolNames.includes("wait_for_activity"), true); });
test("buildReplyToolSet includes note_context when voice tools are available", () => { const toolNames = buildReplyToolSet({ browser: { enabled: false }, webSearch: { enabled: false }, memory: { enabled: false } }, { voiceToolsAvailable: true, conversationSearchAvailable: false }).map((tool) => tool.name);
assert.equal(toolNames.includes("note_context"), true); assert.equal(toolNames.includes("video_search"), true); assert.equal(toolNames.includes("video_play"), true); assert.equal(toolNames.includes("stream_visualizer"), true); assert.equal(toolNames.includes("set_addressing"), false); });
test("buildVoiceRealtimeLocalToolSchemas exposes only voice-executable local tools", () => { const toolNames = buildVoiceRealtimeLocalToolSchemas({ browserAvailable: true, minecraftAvailable: true, memoryAvailable: true, screenShareAvailable: true, screenShareSnapshotAvailable: true, soundboardAvailable: true, webSearchAvailable: true }).map((tool) => tool.name);
assert.equal(toolNames.includes("stream_visualizer"), true); assert.equal(toolNames.includes("video_context"), false); });
test("executeReplyTool delegates web_scrape to readPageSummary", async () => { const calls: Array<{ url: string; maxChars: number }> = [];
const result = await executeReplyTool( "web_scrape", { url: "https://example.com/article" }, { search: { searchAndRead: async () => ({ query: "", results: [] }), readPageSummary: async (url, maxChars) => { calls.push({ url, maxChars }); return { title: "Example Article", summary: "This is the article content.", extractionMethod: "fast" }; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "read this", trace: { source: "reply_message" } } );
assert.equal(result.isError, undefined); assert.match(result.content, /Example Article/); assert.match(result.content, /This is the article content./); assert.equal(calls.length, 1); assert.equal(calls[0].url, "https://example.com/article"); });
test("executeReplyTool web_scrape suggests browser_browse on failure", async () => { const result = await executeReplyTool( "web_scrape", { url: "https://example.com/spa" }, { search: { searchAndRead: async () => ({ query: "", results: [] }), readPageSummary: async () => { throw new Error("HTML page had no usable text"); } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "read this", trace: { source: "reply_message" } } );
assert.equal(result.isError, true); assert.match(result.content, /browser_browse/); });
test("executeReplyTool resolves video_context by VID ref from current message attachments", async () => { const calls: Array<Record<string, unknown>> = [];
const result = await executeReplyTool( "video_context", { videoRef: "VID 1" }, { video: { async fetchContext(opts) { calls.push(opts); return { text: "Title: direct upload", imageInputs: [] }; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "check this upload", trace: { source: "reply_message" }, videoLookup: { refs: { "VID 1": "https://cdn.discordapp.com/attachments/1/2/demo.mp4" } } } );
assert.equal(result.isError, undefined); assert.match(result.content, /Title: direct upload/); assert.deepEqual(calls, [{ url: "https://cdn.discordapp.com/attachments/1/2/demo.mp4", settings: {}, trace: { guildId: "guild-1", channelId: "channel-1", userId: "user-1", source: "video_context_tool" } }]); });
test("executeReplyTool delegates conversation_search to store history search", async () => { const queries: Array<Record<string, unknown>> = [];
const result = await executeReplyTool( "conversation_search", { query: "starter roguelikes", scope: "guild", top_k: 1, max_age_hours: 48 }, { store: { logAction() {}, searchConversationWindows(opts) { queries.push(opts); return [ { ageMinutes: 90, messages: [ { author_name: "alice", content: "you said spelunky 2 was the cleanest starter pick", is_bot: 0 } ] } ]; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "what did we say earlier", trace: { source: "reply_message" } } );
assert.equal(result.isError, undefined); assert.match(result.content, /Conversation history for "starter roguelikes"/); assert.match(result.content, /spelunky 2 was the cleanest starter pick/i); assert.deepEqual(queries, [{ guildId: "guild-1", channelId: null, queryText: "starter roguelikes", limit: 1, maxAgeHours: 48, before: 1, after: 1 }]); });
test("executeReplyTool delegates browser_browse to runtime", async () => { const calls: Array<Record<string, unknown>> = [];
const result = await executeReplyTool( "browser_browse", { query: "check the latest post" }, { browser: { async browse(opts) { calls.push(opts); return { text: "Found the latest post.", imageInputs: [ { mediaType: "image/png", dataBase64: "Zm9v" } ], steps: 3, hitStepLimit: false }; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "browse it", trace: { source: "reply_message" } } );
assert.equal(result.isError, undefined); assert.match(result.content, /Found the latest post./); assert.match(result.content, /Browser screenshot attached for visual inspection./); assert.match(result.content, /Steps: 3/); assert.deepEqual(result.imageInputs, [ { mediaType: "image/png", dataBase64: "Zm9v" } ]); assert.deepEqual(calls, [{ settings: {}, query: "check the latest post", guildId: "guild-1", channelId: "channel-1", userId: "user-1", source: "reply_message", signal: undefined }]); });
test("executeReplyTool omits session_id when a browser session completes itself", async () => { const manager = new SubAgentSessionManager(); const completedSession = { id: "browser:completed:1", type: "browser" as const, createdAt: Date.now(), ownerUserId: "user-1", lastUsedAt: Date.now(), status: "idle" as const, async runTurn() { this.status = "completed"; return { text: "Finished browsing.", costUsd: 0, isError: false, errorMessage: "", sessionCompleted: true, usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 } }; }, cancel() { this.status = "cancelled"; }, close() { if (this.status === "idle" || this.status === "running") { this.status = "cancelled"; } } };
const result = await executeReplyTool( "browser_browse", { query: "finish and close" }, { subAgentSessions: { manager, createBrowserSession() { return completedSession; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "finish this", trace: { source: "reply_message" } } );
assert.equal(result.isError, undefined); assert.equal(result.content.includes("[session_id:"), false); assert.match(result.content, /Finished browsing./); assert.equal(manager.has(completedSession.id), false); });
test("executeReplyTool rejects unauthorized minecraft status access", async () => { const manager = new SubAgentSessionManager(); const session = createMockMinecraftSession({ id: "minecraft:guild-1:channel-1:1:1", ownerUserId: "user-2" }); manager.register(session);
const result = await executeReplyTool( "minecraft_task", { action: "status", session_id: session.id }, { subAgentSessions: { manager, createBrowserSession() { return null; }, createMinecraftSession() { return null; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "status?", trace: { source: "reply_message" } } );
assert.equal(result.isError, true); assert.match(result.content, /Not authorized/); });
test("executeReplyTool reuses the caller's active minecraft session when no session_id is provided", async () => { const manager = new SubAgentSessionManager(); const runInputs: string[] = []; const session = createMockMinecraftSession({ id: "minecraft:guild-1:channel-1:2:1", ownerUserId: "user-1", async onRunTurn(input) { runInputs.push(input); return { text: "Following you.", costUsd: 0, isError: false, errorMessage: "", usage: { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 } }; } }); manager.register(session);
let createCalls = 0; const result = await executeReplyTool( "minecraft_task", { task: "follow me", mode: "companion", constraints: { stay_near_player: "Volpestyle", max_distance: 4 } }, { subAgentSessions: { manager, createBrowserSession() { return null; }, createMinecraftSession() { createCalls += 1; return null; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "follow me", trace: { source: "reply_message" } } );
assert.equal(createCalls, 0); assert.equal(runInputs.length, 1); assert.deepEqual(JSON.parse(runInputs[0] || "{}"), { task: "follow me", mode: "companion", constraints: { stay_near_player: "Volpestyle", max_distance: 4 } }); assert.match(result.content, /Following you./); assert.match(result.content, /[session_id:/); });
test("executeReplyTool blocks new minecraft control when another user already owns the live session", async () => { const manager = new SubAgentSessionManager(); manager.register(createMockMinecraftSession({ id: "minecraft:guild-1:channel-1:3:1", ownerUserId: "user-2" }));
let createCalls = 0; const result = await executeReplyTool( "minecraft_task", { task: "follow me" }, { subAgentSessions: { manager, createBrowserSession() { return null; }, createMinecraftSession() { createCalls += 1; return null; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "follow me", trace: { source: "reply_message" } } );
assert.equal(createCalls, 0); assert.equal(result.isError, true); assert.match(result.content, /already active/); });
test("executeReplyTool cancels the caller's active minecraft session without requiring session_id", async () => { const manager = new SubAgentSessionManager(); const session = createMockMinecraftSession({ id: "minecraft:guild-1:channel-1:4:1", ownerUserId: "user-1" }); manager.register(session);
const result = await executeReplyTool( "minecraft_task", { action: "cancel" }, { subAgentSessions: { manager, createBrowserSession() { return null; }, createMinecraftSession() { return null; } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "stop it", trace: { source: "reply_message" } } );
assert.equal(result.isError, undefined); assert.match(result.content, /cancelled/); assert.equal(manager.has(session.id), false); });
test("executeReplyTool fails music_play with empty query", async () => { const result = await executeReplyTool( "music_play", {}, { voiceSession: { async musicSearch() { throw new Error("not used"); }, async musicPlay() { throw new Error("should not be called"); }, async videoSearch() { throw new Error("not used"); }, async videoPlay() { throw new Error("not used"); }, async musicQueueAdd() { throw new Error("not used"); }, async musicQueueNext() { throw new Error("not used"); }, async musicStop() { throw new Error("not used"); }, async musicPause() { throw new Error("not used"); }, async musicResume() { throw new Error("not used"); }, async musicReplyHandoff() { throw new Error("not used"); }, async musicSkip() { throw new Error("not used"); }, async musicNowPlaying() { throw new Error("not used"); }, async playSoundboard() { throw new Error("not used"); }, async leaveVoiceChannel() { throw new Error("not used"); } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "Yo, can you play me, um... Some yeet.", trace: { source: "voice_turn" } } );
assert.equal(result.isError, true); assert.match(result.content, /query was empty/); });
test("executeReplyTool fails video_play with empty query", async () => { const result = await executeReplyTool( "video_play", {}, { voiceSession: { async musicSearch() { throw new Error("not used"); }, async musicPlay() { throw new Error("not used"); }, async videoSearch() { throw new Error("not used"); }, async videoPlay() { throw new Error("should not be called"); }, async musicQueueAdd() { throw new Error("not used"); }, async musicQueueNext() { throw new Error("not used"); }, async musicStop() { throw new Error("not used"); }, async musicPause() { throw new Error("not used"); }, async musicResume() { throw new Error("not used"); }, async musicReplyHandoff() { throw new Error("not used"); }, async musicSkip() { throw new Error("not used"); }, async musicNowPlaying() { throw new Error("not used"); }, async playSoundboard() { throw new Error("not used"); }, async leaveVoiceChannel() { throw new Error("not used"); } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "play some youtube video", trace: { source: "voice_turn" } } );
assert.equal(result.isError, true); assert.match(result.content, /query was empty/); });
test("executeReplyTool fails music_search with empty query", async () => { const result = await executeReplyTool( "music_search", {}, { voiceSession: { async musicSearch() { throw new Error("should not be called"); }, async musicPlay() { throw new Error("not used"); }, async videoSearch() { throw new Error("not used"); }, async videoPlay() { throw new Error("not used"); }, async musicQueueAdd() { throw new Error("not used"); }, async musicQueueNext() { throw new Error("not used"); }, async musicStop() { throw new Error("not used"); }, async musicPause() { throw new Error("not used"); }, async musicResume() { throw new Error("not used"); }, async musicReplyHandoff() { throw new Error("not used"); }, async musicSkip() { throw new Error("not used"); }, async musicNowPlaying() { throw new Error("not used"); }, async playSoundboard() { throw new Error("not used"); }, async leaveVoiceChannel() { throw new Error("not used"); } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "Search for Yeat", trace: { source: "voice_turn" } } );
assert.equal(result.isError, true); assert.match(result.content, /query was empty/); });
test("executeReplyTool delegates media_reply_handoff to the voice runtime", async () => { const calls: string[] = [];
const result = await executeReplyTool( "media_reply_handoff", { mode: "duck" }, { voiceSession: { async musicSearch() { throw new Error("not used"); }, async musicPlay() { throw new Error("not used"); }, async videoSearch() { throw new Error("not used"); }, async videoPlay() { throw new Error("not used"); }, async musicQueueAdd() { throw new Error("not used"); }, async musicQueueNext() { throw new Error("not used"); }, async musicStop() { throw new Error("not used"); }, async musicPause() { throw new Error("not used"); }, async musicResume() { throw new Error("not used"); }, async musicReplyHandoff(mode) { calls.push(mode); return { ok: true, mode, applied: true }; }, async musicSkip() { throw new Error("not used"); }, async musicNowPlaying() { throw new Error("not used"); }, async playSoundboard() { throw new Error("not used"); }, async leaveVoiceChannel() { throw new Error("not used"); } } }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "talk over the song for a second", trace: { source: "voice_turn" } } );
assert.equal(result.isError, undefined); assert.deepEqual(calls, ["duck"]); assert.match(result.content, /"mode":"duck"/); });
test("executeReplyTool delegates stream_visualizer to the voice runtime", async () => { const calls: Array<string | null> = [];
const result = await executeReplyTool( "stream_visualizer", { mode: "waves" }, { voiceSession: createMockVoiceSession({ async streamVisualizer(mode) { calls.push(mode ?? null); return { ok: true, mode }; } }) }, { settings: {}, guildId: "guild-1", channelId: "channel-1", userId: "user-1", sourceMessageId: "msg-1", sourceText: "turn on the visualizer", trace: { source: "voice_turn" } } );
assert.equal(result.isError, undefined); assert.deepEqual(calls, ["waves"]); assert.match(result.content, /"mode":"waves"/); });
