import type { PrismaClient } from "@rakazo/db"; import { describe, expect, it } from "vitest"; import { COACH_TOOL_NAMES, executeCoachTool } from "./coach-tools.js"; import { selectBuiltinToolsForRun } from "./executor-tools.js"; function toolNames(options: { mainBot?: boolean; messagingChannelRun?: boolean; foreignAgentRun?: boolean; trigger?: string; }) { return selectBuiltinToolsForRun({ graphicalToolsAllowed: false, groupId: null, trigger: options.trigger ?? "user", semanticMemoryEnabled: false, messagingChannelRun: options.messagingChannelRun ?? false, foreignAgentRun: options.foreignAgentRun, bot: { kind: options.mainBot ? "main" : "specialist", restrictBuiltinTools: false, allowedBuiltinTools: [], }, }).map((tool) => tool.name); } /** Any database access fails the test: refusals and invalid input must not reach it. */ const untouchedPrisma = new Proxy( {}, { get(_target, property) { throw new Error(`unexpected database access: ${String(property)}`); }, }, ) as PrismaClient; const context = (kind: string) => ({ userId: "user-1", bot: { id: "bot-1", kind } }); describe("coach tools", () => { it("are the main bot's alone, also in its check-in routines, and never for other people", () => { for (const name of COACH_TOOL_NAMES) { expect(toolNames({ mainBot: true })).toContain(name); expect(toolNames({ mainBot: true, trigger: "routine" })).toContain(name); expect(toolNames({ mainBot: false })).not.toContain(name); expect(toolNames({ mainBot: true, messagingChannelRun: true })).not.toContain(name); expect(toolNames({ mainBot: true, foreignAgentRun: true })).not.toContain(name); } }); it("refuse specialists even when they call a tool anyway", async () => { for (const name of COACH_TOOL_NAMES) { await expect( executeCoachTool(untouchedPrisma, context("specialist"), name, { title: "Run" }), ).resolves.toEqual({ error: "Only the user's main bot can coach the user." }); } }); it("reject invalid or incomplete arguments before writing", async () => { const main = context("main"); await expect(executeCoachTool(untouchedPrisma, main, "update_goal", {})).resolves.toEqual({ error: "A new goal needs a title.", }); await expect( executeCoachTool(untouchedPrisma, main, "update_goal", { title: "x", target_date: "soon" }), ).resolves.toEqual({ error: expect.stringContaining("target_date") }); await expect( executeCoachTool(untouchedPrisma, main, "update_habit", { title: "Walk", cadence: "hourly" }), ).resolves.toEqual({ error: expect.stringContaining("cadence") }); await expect( executeCoachTool(untouchedPrisma, main, "log_check_in", { habit: "Walk", goal: "10k" }), ).resolves.toEqual({ error: "Log a habit and a goal in separate calls." }); await expect( executeCoachTool(untouchedPrisma, main, "log_check_in", { mood: 9 }), ).resolves.toEqual({ error: expect.stringContaining("mood") }); await expect( executeCoachTool(untouchedPrisma, main, "write_daily_brief", { text: " " }), ).resolves.toEqual({ error: expect.stringContaining("text") }); }); });