evOo-local/packages/adapters/src/executor-tools.ts
Claude 848abc757a
Merge capabilities: least-privilege builtin tools per bot and schedules on behalf
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
2026-10-02 23:53:19 +00:00

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,
);
}