The deployment owner adds folders of photos and videos in the media settings. A per-folder background job indexes them (relative path, kind, size, capture date, dimensions, duration), re-indexes every six hours, and drops files that are gone. The owner's own runs get two tools: search_media (words in folder and file names, kind, capture date range, newest first) and import_media (copies one file into the conversation as an artifact). Files are never changed; hidden entries and symlinks are skipped, and an import must resolve inside its folder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs
132 lines
5.0 KiB
TypeScript
132 lines
5.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { agentConnectionTools, builtinAgentTools } from "./builtin-tools.js";
|
|
import {
|
|
builtinToolNamesForBot,
|
|
filterBuiltinToolsForBot,
|
|
MAIN_ONLY_BUILTIN_TOOLS,
|
|
PERSONAL_AGENT_TOOLS,
|
|
selectBuiltinToolsForRun,
|
|
unknownBuiltinToolNames,
|
|
} from "./executor-tools.js";
|
|
|
|
const unrestricted = { kind: "specialist", restrictBuiltinTools: false, allowedBuiltinTools: [] };
|
|
|
|
function select(
|
|
bot: { kind: string; restrictBuiltinTools: boolean; allowedBuiltinTools: string[] },
|
|
options: {
|
|
graphicalToolsAllowed?: boolean;
|
|
agentConnectionsEnabled?: boolean;
|
|
mediaLibraryEnabled?: boolean;
|
|
foreignAgentRun?: boolean;
|
|
messagingChannelRun?: boolean;
|
|
} = {},
|
|
) {
|
|
return selectBuiltinToolsForRun({
|
|
graphicalToolsAllowed: options.graphicalToolsAllowed ?? true,
|
|
groupId: null,
|
|
trigger: "user",
|
|
semanticMemoryEnabled: false,
|
|
messagingChannelRun: options.messagingChannelRun ?? false,
|
|
agentConnectionsEnabled: options.agentConnectionsEnabled,
|
|
mediaLibraryEnabled: options.mediaLibraryEnabled,
|
|
foreignAgentRun: options.foreignAgentRun,
|
|
bot,
|
|
}).map((tool) => tool.name);
|
|
}
|
|
|
|
/** Every builtin a specialist can get: the catalog without the main bot's own tools. */
|
|
const specialistToolNames = builtinAgentTools
|
|
.map((tool) => tool.name)
|
|
.filter((name) => !MAIN_ONLY_BUILTIN_TOOLS.has(name));
|
|
|
|
describe("builtin tools per bot", () => {
|
|
it("adds agent connection tools only for chat-linked bots", () => {
|
|
expect(select(unrestricted)).not.toContain("message_agent");
|
|
expect(select(unrestricted, { agentConnectionsEnabled: true })).toEqual(
|
|
expect.arrayContaining(agentConnectionTools.map((tool) => tool.name)),
|
|
);
|
|
});
|
|
|
|
it("offers the media library tools only to the user's own runs once a folder exists", () => {
|
|
const library = ["search_media", "import_media"];
|
|
expect(select(unrestricted)).not.toEqual(expect.arrayContaining(["search_media"]));
|
|
expect(select(unrestricted, { mediaLibraryEnabled: true })).toEqual(
|
|
expect.arrayContaining(library),
|
|
);
|
|
for (const run of [{ foreignAgentRun: true }, { messagingChannelRun: true }]) {
|
|
const names = select(unrestricted, { mediaLibraryEnabled: true, ...run });
|
|
expect(names.filter((name) => library.includes(name))).toEqual([]);
|
|
}
|
|
});
|
|
|
|
it("applies the allowlist to agent connection tools too", () => {
|
|
const bot = {
|
|
kind: "specialist",
|
|
restrictBuiltinTools: true,
|
|
allowedBuiltinTools: ["message_agent", "read_file"],
|
|
};
|
|
expect(select(bot, { agentConnectionsEnabled: true })).toEqual(["read_file", "message_agent"]);
|
|
});
|
|
|
|
it("never brings back a tool the run itself withholds", () => {
|
|
const bot = {
|
|
kind: "specialist",
|
|
restrictBuiltinTools: true,
|
|
allowedBuiltinTools: ["computer_observe", "read_file"],
|
|
};
|
|
expect(select(bot, { graphicalToolsAllowed: false })).toEqual(["read_file"]);
|
|
});
|
|
|
|
it("ignores the allowlist while the restriction is off", () => {
|
|
const bot = { ...unrestricted, allowedBuiltinTools: ["read_file"] };
|
|
expect(filterBuiltinToolsForBot(builtinAgentTools, bot).map((tool) => tool.name)).toEqual(
|
|
specialistToolNames,
|
|
);
|
|
});
|
|
|
|
it("lists main-only tools for the main bot alone", () => {
|
|
expect([...MAIN_ONLY_BUILTIN_TOOLS].sort()).toEqual([
|
|
"delegate_task",
|
|
"log_check_in",
|
|
"notify_user",
|
|
"update_goal",
|
|
"update_habit",
|
|
"update_user_profile",
|
|
"write_daily_brief",
|
|
]);
|
|
const specialist = builtinToolNamesForBot({ kind: "specialist", chatLinked: false });
|
|
const main = builtinToolNamesForBot({ kind: "main", chatLinked: false });
|
|
for (const name of MAIN_ONLY_BUILTIN_TOOLS) {
|
|
expect(specialist).not.toContain(name);
|
|
expect(main).toContain(name);
|
|
}
|
|
});
|
|
|
|
it("keeps the personal agent's tools from a project's main bot", () => {
|
|
const main = { kind: "main", restrictBuiltinTools: false, allowedBuiltinTools: [] };
|
|
const personal = filterBuiltinToolsForBot(builtinAgentTools, { ...main, personalAgent: true });
|
|
const lead = filterBuiltinToolsForBot(builtinAgentTools, main).map((tool) => tool.name);
|
|
expect(personal.map((tool) => tool.name)).toEqual(
|
|
expect.arrayContaining([...PERSONAL_AGENT_TOOLS]),
|
|
);
|
|
for (const name of PERSONAL_AGENT_TOOLS) expect(lead).not.toContain(name);
|
|
expect(lead).toEqual(expect.arrayContaining(["delegate_task", "update_user_profile"]));
|
|
});
|
|
|
|
it("lists agent connection tools only for chat-linked bots", () => {
|
|
expect(builtinToolNamesForBot({ kind: "specialist", chatLinked: false })).toEqual(
|
|
specialistToolNames,
|
|
);
|
|
expect(builtinToolNamesForBot({ kind: "specialist", chatLinked: true })).toEqual([
|
|
...specialistToolNames,
|
|
...agentConnectionTools.map((tool) => tool.name),
|
|
]);
|
|
});
|
|
|
|
it("flags names that are not builtin tools", () => {
|
|
expect(
|
|
unknownBuiltinToolNames(["web_search", "message_agent", "web_serach", "mcp__notion__search"]),
|
|
).toEqual(["web_serach", "mcp__notion__search"]);
|
|
});
|
|
});
|