The main agent had no way to record what the user wants to achieve or how their day went, and its prompt never showed their goals or habits. - update_goal, update_habit, log_check_in and write_daily_brief, main-bot only and, like update_user_profile, withheld from shared channels and from runs that answer another owner's agent. Goals and habits can be named by id or exact title. - The main bot's own runs list active goals and habits (with ids, done state and streak) in the <user> block; the basics-only block and every other bot never see them. The main agent role adds a short coaching stance and names each coach tool only when the run has it, so the check-in routine prompts stay tool-agnostic. - The scripted runtime understands a few coach commands and the morning check-in, so tests can drive the tools end to end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
80 lines
3.2 KiB
TypeScript
80 lines
3.2 KiB
TypeScript
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") });
|
|
});
|
|
});
|