The per-bot tool policy now carries the main bot's own tools: delegate_task and update_user_profile are the main-only builtins, so the separate filters from the profile and delegation branches are gone. One set of exposed tool names drives both the executor's call guard and the prompt. The schedule tool says new routines default to the user's time zone, which makes the extra prompt line unnecessary. Tests that relied on an empty tool list meaning every tool, or on the tool's own refusal text, now pass the tool explicitly or expect the executor's refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
124 lines
4.3 KiB
TypeScript
124 lines
4.3 KiB
TypeScript
import type { ConnectorTool } from "@rakazo/adapter-kit";
|
|
import { agentConnectionTools, builtinAgentTools } from "./builtin-tools.js";
|
|
import { selectCloudAgentTools } from "./cloud-agent-tools-select.js";
|
|
import { selectMemoryTools } from "./memory-tools.js";
|
|
import { filterImageReturningComputerTools } from "./model-vision.js";
|
|
import {
|
|
filterBuiltinToolsForRun,
|
|
filterBuiltinToolsForThread,
|
|
withScheduleTargetBot,
|
|
} from "./schedule-tools.js";
|
|
|
|
/** Browser tools that require a graphical computer (Chrome). */
|
|
export const PAGE_BROWSER_TOOL_NAMES = new Set([
|
|
"browser_navigate",
|
|
"browser_snapshot",
|
|
"browser_act",
|
|
]);
|
|
|
|
/** Builtin tools only the user's main bot may use; every other bot's runs never see them. */
|
|
export const MAIN_ONLY_BUILTIN_TOOLS: ReadonlySet<string> = new Set<string>([
|
|
"delegate_task",
|
|
"update_user_profile",
|
|
]);
|
|
|
|
const BUILTIN_TOOL_NAMES = [...builtinAgentTools, ...agentConnectionTools].map((tool) => tool.name);
|
|
|
|
/** The bot fields that decide which builtin tools its runs may use. */
|
|
export interface BuiltinToolPolicy {
|
|
kind: string;
|
|
restrictBuiltinTools: boolean;
|
|
allowedBuiltinTools: readonly string[];
|
|
}
|
|
|
|
/**
|
|
* Builtin tool names a bot's allowlist can choose from, in catalog order. Agent connection tools
|
|
* only ever reach chat-linked bots, so only those list them.
|
|
*/
|
|
export function builtinToolNamesForBot(bot: { kind: string; chatLinked: boolean }): string[] {
|
|
return [...builtinAgentTools, ...(bot.chatLinked ? agentConnectionTools : [])]
|
|
.map((tool) => tool.name)
|
|
.filter((name) => bot.kind === "main" || !MAIN_ONLY_BUILTIN_TOOLS.has(name));
|
|
}
|
|
|
|
/** Names that are not builtin tools; an allowlist must not carry typos or connector tools. */
|
|
export function unknownBuiltinToolNames(names: readonly string[]): string[] {
|
|
return names.filter((name) => !BUILTIN_TOOL_NAMES.includes(name));
|
|
}
|
|
|
|
/**
|
|
* Least privilege per bot: main-only tools stay with the main bot, a restricted bot keeps only
|
|
* its allowlist (empty means none), and the main bot may schedule for another bot.
|
|
*/
|
|
export function filterBuiltinToolsForBot(
|
|
tools: ConnectorTool[],
|
|
bot: BuiltinToolPolicy,
|
|
): ConnectorTool[] {
|
|
const main = bot.kind === "main";
|
|
const allowed = new Set(bot.allowedBuiltinTools);
|
|
return tools
|
|
.filter(
|
|
(tool) =>
|
|
(main || !MAIN_ONLY_BUILTIN_TOOLS.has(tool.name)) &&
|
|
(!bot.restrictBuiltinTools || allowed.has(tool.name)),
|
|
)
|
|
.map((tool) => (main && tool.name === "schedule_create" ? withScheduleTargetBot(tool) : tool));
|
|
}
|
|
|
|
export function filterPageBrowserTools<T extends { name: string }>(
|
|
tools: T[],
|
|
pageBrowserAllowed: boolean,
|
|
): T[] {
|
|
if (pageBrowserAllowed) return tools;
|
|
return tools.filter((tool) => !PAGE_BROWSER_TOOL_NAMES.has(tool.name));
|
|
}
|
|
|
|
export function selectBuiltinToolsForRun(options: {
|
|
graphicalToolsAllowed: boolean;
|
|
/** Page browser tools need a graphical computer (Chrome), not model vision. */
|
|
pageBrowserAllowed?: boolean;
|
|
groupId: string | null;
|
|
trigger: string;
|
|
semanticMemoryEnabled: boolean;
|
|
cloudAgentEnabled?: boolean;
|
|
messagingChannelRun: boolean;
|
|
/** Cross-owner agent connections only exist for chat-linked bots. */
|
|
agentConnectionsEnabled?: boolean;
|
|
/** Answering another owner's agent: what the run hears is not the user, so no profile writes. */
|
|
foreignAgentRun?: boolean;
|
|
bot: BuiltinToolPolicy;
|
|
}) {
|
|
const selected = selectCloudAgentTools(
|
|
selectMemoryTools(
|
|
filterBuiltinToolsForRun(
|
|
filterBuiltinToolsForThread(
|
|
filterPageBrowserTools(
|
|
filterImageReturningComputerTools(builtinAgentTools, options.graphicalToolsAllowed),
|
|
options.pageBrowserAllowed ?? options.graphicalToolsAllowed,
|
|
),
|
|
options.groupId,
|
|
),
|
|
options.trigger,
|
|
),
|
|
options.semanticMemoryEnabled,
|
|
),
|
|
Boolean(options.cloudAgentEnabled),
|
|
).filter(
|
|
(tool) =>
|
|
!(options.foreignAgentRun && tool.name === "update_user_profile") &&
|
|
(!options.messagingChannelRun ||
|
|
(![
|
|
"remember",
|
|
"save_memory",
|
|
"recall_memory",
|
|
"forget_memory",
|
|
"update_user_profile",
|
|
].includes(tool.name) &&
|
|
!tool.name.startsWith("scratchpad_"))),
|
|
);
|
|
return filterBuiltinToolsForBot(
|
|
options.agentConnectionsEnabled ? [...selected, ...agentConnectionTools] : selected,
|
|
options.bot,
|
|
);
|
|
}
|