From 087d1a8653b61444b63cd84d4298cc2cda1b44aa Mon Sep 17 00:00:00 2001 From: Jiang Bohan Date: Thu, 5 Feb 2026 02:58:15 +0800 Subject: [PATCH] refactor(tools): remove tool profile layer from policy system Simplify 4-layer policy to 3-layer: - Layer 1: Global allow/deny (user config) - Layer 2: Provider-specific rules - Layer 3: Subagent restrictions Removed: - ToolProfileId type (minimal/coding/web/full) - TOOL_PROFILES constant - getProfilePolicy function - profile field from ToolsConfig Users can achieve the same effect using allow/deny with group:* syntax. Co-Authored-By: Claude Opus 4.5 --- src/agent/tools/README.md | 151 ++++++++++---------------------- src/agent/tools/README.zh-CN.md | 151 ++++++++++---------------------- src/agent/tools/groups.ts | 49 +---------- src/agent/tools/index.ts | 5 +- src/agent/tools/policy.test.ts | 72 +++++---------- src/agent/tools/policy.ts | 61 ++++--------- 6 files changed, 128 insertions(+), 361 deletions(-) diff --git a/src/agent/tools/README.md b/src/agent/tools/README.md index 80087453..db5b5266 100644 --- a/src/agent/tools/README.md +++ b/src/agent/tools/README.md @@ -19,28 +19,22 @@ The tools system provides LLM agents with capabilities to interact with the exte │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ 4-Layer Policy Filter │ +│ 3-Layer Policy Filter │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Layer 1: Profile │ │ -│ │ Base tool set: minimal | coding | web | full │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Layer 2: Global Allow/Deny │ │ +│ │ Layer 1: Global Allow/Deny │ │ │ │ User customization via CLI or config │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Layer 3: Provider-Specific │ │ +│ │ Layer 2: Provider-Specific │ │ │ │ Different rules for different LLM providers │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ Layer 4: Subagent Restrictions │ │ +│ │ Layer 3: Subagent Restrictions │ │ │ │ Limited tools for spawned child agents │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ @@ -55,20 +49,20 @@ The tools system provides LLM agents with capabilities to interact with the exte ## Available Tools -| Tool | Name | Description | -| ------------- | --------------- | --------------------------------------------- | -| Read | `read` | Read file contents | -| Write | `write` | Write content to files | -| Edit | `edit` | Edit existing files | -| Glob | `glob` | Find files by pattern | -| Exec | `exec` | Execute shell commands | -| Process | `process` | Manage long-running processes | -| Web Fetch | `web_fetch` | Fetch and extract content from URLs | -| Web Search | `web_search` | Search the web (requires API key) | -| Memory Get | `memory_get` | Retrieve a value from persistent memory | -| Memory Set | `memory_set` | Store a value in persistent memory | -| Memory Delete | `memory_delete` | Delete a value from persistent memory | -| Memory List | `memory_list` | List all keys in persistent memory | +| Tool | Name | Description | +| ------------- | --------------- | --------------------------------------- | +| Read | `read` | Read file contents | +| Write | `write` | Write content to files | +| Edit | `edit` | Edit existing files | +| Glob | `glob` | Find files by pattern | +| Exec | `exec` | Execute shell commands | +| Process | `process` | Manage long-running processes | +| Web Fetch | `web_fetch` | Fetch and extract content from URLs | +| Web Search | `web_search` | Search the web (requires API key) | +| Memory Get | `memory_get` | Retrieve a value from persistent memory | +| Memory Set | `memory_set` | Store a value in persistent memory | +| Memory Delete | `memory_delete` | Delete a value from persistent memory | +| Memory List | `memory_list` | List all keys in persistent memory | > **Note**: Memory tools require a `profileId` to be specified. They store data in the profile's memory directory. @@ -76,24 +70,13 @@ The tools system provides LLM agents with capabilities to interact with the exte Groups provide shortcuts for allowing/denying multiple tools at once: -| Group | Tools | -| --------------- | ------------------------------------------------- | -| `group:fs` | read, write, edit, glob | -| `group:runtime` | exec, process | -| `group:web` | web_search, web_fetch | -| `group:memory` | memory_get, memory_set, memory_delete, memory_list| -| `group:core` | All of the above (excluding memory) | - -## Tool Profiles - -Profiles are predefined tool sets for common use cases: - -| Profile | Description | Tools | -| --------- | ----------------------- | ---------------------------------- | -| `minimal` | No tools (chat-only) | None | -| `coding` | File system + execution | group:fs, group:runtime | -| `web` | Coding + web access | group:fs, group:runtime, group:web | -| `full` | No restrictions | All tools | +| Group | Tools | +| --------------- | -------------------------------------------------- | +| `group:fs` | read, write, edit, glob | +| `group:runtime` | exec, process | +| `group:web` | web_search, web_fetch | +| `group:memory` | memory_get, memory_set, memory_delete, memory_list | +| `group:core` | All of the above (excluding memory) | ## Usage @@ -102,11 +85,8 @@ Profiles are predefined tool sets for common use cases: All commands use the unified `multica` CLI (or `pnpm multica` during development). ```bash -# Use a specific profile -multica run --tools-profile coding "list files" - -# Minimal profile with specific tools allowed -multica run --tools-profile minimal --tools-allow exec "run ls" +# Allow only specific tools +multica run --tools-allow group:fs,group:runtime "list files" # Deny specific tools multica run --tools-deny exec,process "read file.txt" @@ -122,14 +102,11 @@ import { Agent } from './runner.js'; const agent = new Agent({ tools: { - // Layer 1: Base profile - profile: 'coding', + // Layer 1: Global allow/deny + allow: ['group:fs', 'group:runtime', 'web_fetch'], + deny: ['exec'], - // Layer 2: Global customization - allow: ['web_fetch'], // Add web_fetch to coding profile - deny: ['exec'], // But deny exec - - // Layer 3: Provider-specific rules + // Layer 2: Provider-specific rules byProvider: { google: { deny: ['exec', 'process'], // Google models can't use runtime tools @@ -137,7 +114,7 @@ const agent = new Agent({ }, }, - // Layer 4: Subagent mode + // Layer 3: Subagent mode isSubagent: false, }); ``` @@ -150,43 +127,28 @@ Use the tools CLI to inspect and test configurations: # List all available tools multica tools list -# List tools after applying a profile -multica tools list --profile coding +# List tools with allow rules +multica tools list --allow group:fs,group:runtime # List tools with deny rules -multica tools list --profile coding --deny exec +multica tools list --deny exec # Show all tool groups multica tools groups - -# Show all profiles -multica tools profiles ``` ## Policy System Details -### Layer 1: Profile +### Layer 1: Global Allow/Deny -The profile determines the base set of available tools. If not specified, all tools are available. +User-specified allow/deny lists: -```typescript -// In groups.ts -export const TOOL_PROFILES = { - minimal: { allow: [] }, // No tools - coding: { allow: ['group:fs', 'group:runtime'] }, // FS + execution - web: { allow: ['group:fs', 'group:runtime', 'group:web'] }, // + web - full: {}, // No restrictions -}; -``` +- `allow`: Only these tools are available (supports group:\* syntax) +- `deny`: These tools are blocked (takes precedence over allow) -### Layer 2: Global Allow/Deny +If no `allow` list is specified, all tools are available by default. -User-specified allow/deny lists that modify the profile's tool set: - -- `allow`: Only these tools are available (additive to profile) -- `deny`: These tools are blocked (takes precedence over allow) - -### Layer 3: Provider-Specific +### Layer 2: Provider-Specific Different LLM providers may have different capabilities or restrictions: @@ -199,7 +161,7 @@ Different LLM providers may have different capabilities or restrictions: } ``` -### Layer 4: Subagent Restrictions +### Layer 3: Subagent Restrictions When `isSubagent: true`, additional restrictions are applied to prevent spawned agents from accessing sensitive tools like session management. @@ -280,7 +242,7 @@ Tools configuration can be defined in Agent Profile's `config.json`, allowing di │ │ coder │ │ reviewer │ │ devops │ │ │ │ │ │ │ │ │ │ │ │ tools: │ │ tools: │ │ tools: │ │ -│ │ coding │ │ minimal │ │ full │ │ +│ │ allow:fs │ │ deny:* │ │ allow:* │ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ └─────────┼────────────────┼────────────────┼─────────────────────┘ @@ -296,7 +258,7 @@ Each Agent's Profile can define its own tools configuration in `config.json`: ```json { "tools": { - "profile": "coding", + "allow": ["group:fs", "group:runtime"], "deny": ["exec"] }, "provider": "anthropic", @@ -305,28 +267,3 @@ Each Agent's Profile can define its own tools configuration in `config.json`: ``` See [Profile README](../profile/README.md) for full documentation. - -### Config Priority - -When both Profile config and CLI options are provided: - -1. **Profile `config.json`** - Base configuration -2. **CLI options** - Override/extend profile settings - -```bash -# Profile has tools.profile = "coding" -# CLI adds --tools-deny exec -# Result: coding profile without exec tool -multica run --profile my-agent --tools-deny exec "list files" -``` - -## Future Tools - -The following tools are planned for future implementation: - -- **Browser** - Simplified web automation (screenshot, click, type) -- **Session Management** - `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` -- **Image** - Image generation and manipulation -- **Cron** - Scheduled task execution -- **Message** - Inter-agent communication -- **Canvas** - Visual output generation diff --git a/src/agent/tools/README.zh-CN.md b/src/agent/tools/README.zh-CN.md index 5ac1a99e..80d84815 100644 --- a/src/agent/tools/README.zh-CN.md +++ b/src/agent/tools/README.zh-CN.md @@ -19,28 +19,22 @@ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ 4 层策略过滤器 │ +│ 3 层策略过滤器 │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ 第 1 层: Profile │ │ -│ │ 基础工具集: minimal | coding | web | full │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ 第 2 层: 全局 Allow/Deny │ │ +│ │ 第 1 层: 全局 Allow/Deny │ │ │ │ 通过 CLI 或配置文件进行用户自定义 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ 第 3 层: Provider 特定规则 │ │ +│ │ 第 2 层: Provider 特定规则 │ │ │ │ 不同 LLM Provider 有不同的规则 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ -│ │ 第 4 层: Subagent 限制 │ │ +│ │ 第 3 层: Subagent 限制 │ │ │ │ 子 Agent 的工具访问受限 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ @@ -55,20 +49,20 @@ ## 可用工具 -| 工具 | 名称 | 描述 | -| ------------- | --------------- | --------------------------------------------- | -| Read | `read` | 读取文件内容 | -| Write | `write` | 写入文件内容 | -| Edit | `edit` | 编辑现有文件 | -| Glob | `glob` | 按模式查找文件 | -| Exec | `exec` | 执行 Shell 命令 | -| Process | `process` | 管理长时间运行的进程 | -| Web Fetch | `web_fetch` | 从 URL 获取并提取内容 | -| Web Search | `web_search` | 搜索网络(需要 API Key) | -| Memory Get | `memory_get` | 从持久化内存中获取值 | -| Memory Set | `memory_set` | 向持久化内存中存储值 | -| Memory Delete | `memory_delete` | 从持久化内存中删除值 | -| Memory List | `memory_list` | 列出持久化内存中的所有键 | +| 工具 | 名称 | 描述 | +| ------------- | --------------- | ------------------------ | +| Read | `read` | 读取文件内容 | +| Write | `write` | 写入文件内容 | +| Edit | `edit` | 编辑现有文件 | +| Glob | `glob` | 按模式查找文件 | +| Exec | `exec` | 执行 Shell 命令 | +| Process | `process` | 管理长时间运行的进程 | +| Web Fetch | `web_fetch` | 从 URL 获取并提取内容 | +| Web Search | `web_search` | 搜索网络(需要 API Key) | +| Memory Get | `memory_get` | 从持久化内存中获取值 | +| Memory Set | `memory_set` | 向持久化内存中存储值 | +| Memory Delete | `memory_delete` | 从持久化内存中删除值 | +| Memory List | `memory_list` | 列出持久化内存中的所有键 | > **注意**: Memory 工具需要指定 `profileId`。数据存储在 Profile 的 memory 目录中。 @@ -76,24 +70,13 @@ 工具组提供了一次性允许/禁止多个工具的快捷方式: -| 组 | 工具 | -| --------------- | ------------------------------------------------- | -| `group:fs` | read, write, edit, glob | -| `group:runtime` | exec, process | -| `group:web` | web_search, web_fetch | -| `group:memory` | memory_get, memory_set, memory_delete, memory_list| -| `group:core` | 以上所有(不包括 memory) | - -## 工具配置文件 - -配置文件是为常见用例预定义的工具集: - -| Profile | 描述 | 工具 | -| --------- | ------------------- | ---------------------------------- | -| `minimal` | 无工具(仅聊天) | 无 | -| `coding` | 文件系统 + 执行 | group:fs, group:runtime | -| `web` | 编码 + 网络访问 | group:fs, group:runtime, group:web | -| `full` | 无限制 | 所有工具 | +| 组 | 工具 | +| --------------- | -------------------------------------------------- | +| `group:fs` | read, write, edit, glob | +| `group:runtime` | exec, process | +| `group:web` | web_search, web_fetch | +| `group:memory` | memory_get, memory_set, memory_delete, memory_list | +| `group:core` | 以上所有(不包括 memory) | ## 使用方法 @@ -102,11 +85,8 @@ 所有命令使用统一的 `multica` CLI(开发时使用 `pnpm multica`)。 ```bash -# 使用特定配置文件 -multica run --tools-profile coding "list files" - -# 最小配置文件 + 允许特定工具 -multica run --tools-profile minimal --tools-allow exec "run ls" +# 只允许特定工具 +multica run --tools-allow group:fs,group:runtime "list files" # 禁止特定工具 multica run --tools-deny exec,process "read file.txt" @@ -122,14 +102,11 @@ import { Agent } from './runner.js'; const agent = new Agent({ tools: { - // 第 1 层: 基础配置文件 - profile: 'coding', + // 第 1 层: 全局 allow/deny + allow: ['group:fs', 'group:runtime', 'web_fetch'], + deny: ['exec'], - // 第 2 层: 全局自定义 - allow: ['web_fetch'], // 在 coding 配置文件基础上添加 web_fetch - deny: ['exec'], // 但禁止 exec - - // 第 3 层: Provider 特定规则 + // 第 2 层: Provider 特定规则 byProvider: { google: { deny: ['exec', 'process'], // Google 模型不能使用运行时工具 @@ -137,7 +114,7 @@ const agent = new Agent({ }, }, - // 第 4 层: Subagent 模式 + // 第 3 层: Subagent 模式 isSubagent: false, }); ``` @@ -150,43 +127,28 @@ const agent = new Agent({ # 列出所有可用工具 multica tools list -# 列出应用配置文件后的工具 -multica tools list --profile coding +# 列出带有允许规则的工具 +multica tools list --allow group:fs,group:runtime # 列出带有禁止规则的工具 -multica tools list --profile coding --deny exec +multica tools list --deny exec # 显示所有工具组 multica tools groups - -# 显示所有配置文件 -multica tools profiles ``` ## 策略系统详情 -### 第 1 层: Profile +### 第 1 层: 全局 Allow/Deny -配置文件决定了可用工具的基础集合。如果未指定,则所有工具都可用。 +用户指定的 allow/deny 列表: -```typescript -// 在 groups.ts 中 -export const TOOL_PROFILES = { - minimal: { allow: [] }, // 无工具 - coding: { allow: ['group:fs', 'group:runtime'] }, // 文件系统 + 执行 - web: { allow: ['group:fs', 'group:runtime', 'group:web'] }, // + 网络 - full: {}, // 无限制 -}; -``` +- `allow`: 只有这些工具可用(支持 group:\* 语法) +- `deny`: 这些工具被阻止(优先于 allow) -### 第 2 层: 全局 Allow/Deny +如果未指定 `allow` 列表,默认所有工具都可用。 -用户指定的 allow/deny 列表,用于修改配置文件的工具集: - -- `allow`: 只有这些工具可用(在配置文件基础上添加) -- `deny`: 这些工具被阻止(优先于 allow) - -### 第 3 层: Provider 特定规则 +### 第 2 层: Provider 特定规则 不同的 LLM Provider 可能有不同的能力或限制: @@ -199,7 +161,7 @@ export const TOOL_PROFILES = { } ``` -### 第 4 层: Subagent 限制 +### 第 3 层: Subagent 限制 当 `isSubagent: true` 时,会应用额外的限制,防止子 Agent 访问敏感工具(如会话管理)。 @@ -280,7 +242,7 @@ pnpm test src/agent/tools/policy.test.ts │ │ coder │ │ reviewer │ │ devops │ │ │ │ │ │ │ │ │ │ │ │ tools: │ │ tools: │ │ tools: │ │ -│ │ coding │ │ minimal │ │ full │ │ +│ │ allow:fs │ │ deny:* │ │ allow:* │ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ └─────────┼────────────────┼────────────────┼─────────────────────┘ @@ -296,7 +258,7 @@ pnpm test src/agent/tools/policy.test.ts ```json { "tools": { - "profile": "coding", + "allow": ["group:fs", "group:runtime"], "deny": ["exec"] }, "provider": "anthropic", @@ -305,28 +267,3 @@ pnpm test src/agent/tools/policy.test.ts ``` 详见 [Profile README](../profile/README.md)。 - -### 配置优先级 - -当同时提供 Profile 配置和 CLI 选项时: - -1. **Profile `config.json`** - 基础配置 -2. **CLI 选项** - 覆盖/扩展 Profile 设置 - -```bash -# Profile 有 tools.profile = "coding" -# CLI 添加 --tools-deny exec -# 结果: coding 配置文件但没有 exec 工具 -multica run --profile my-agent --tools-deny exec "list files" -``` - -## 未来工具 - -以下工具计划在未来实现: - -- **Browser** - 简化的网页自动化(截图、点击、输入) -- **Session Management** - `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status` -- **Image** - 图像生成和处理 -- **Cron** - 定时任务执行 -- **Message** - Agent 间通信 -- **Canvas** - 可视化输出生成 diff --git a/src/agent/tools/groups.ts b/src/agent/tools/groups.ts index 1e9edf6c..dde6b5a0 100644 --- a/src/agent/tools/groups.ts +++ b/src/agent/tools/groups.ts @@ -1,12 +1,10 @@ /** - * Tool groups and profiles for policy-based filtering. + * Tool groups for policy-based filtering. * * Groups provide shortcuts for allowing/denying multiple tools at once. - * Profiles are predefined tool sets for common use cases. + * Use "group:name" in allow/deny lists. */ -export type ToolProfileId = "minimal" | "coding" | "web" | "full"; - /** * Tool name aliases for compatibility. * Maps alternative names to canonical tool names. @@ -51,29 +49,6 @@ export const TOOL_GROUPS: Record = { ], }; -/** - * Tool profiles - predefined tool sets. - */ -export const TOOL_PROFILES: Record = { - // Minimal: no tools (useful for chat-only agents) - minimal: { - allow: [], - }, - - // Coding: file system + execution (default for coding tasks) - coding: { - allow: ["group:fs", "group:runtime"], - }, - - // Web: coding + web access - web: { - allow: ["group:fs", "group:runtime", "group:web"], - }, - - // Full: no restrictions - full: {}, -}; - /** * Default tools denied for subagents. * Subagents should not have access to session management or system tools. @@ -118,23 +93,3 @@ export function expandToolGroups(list?: string[]): string[] { return Array.from(new Set(expanded)); } - -/** - * Get the policy for a profile. - */ -export function getProfilePolicy( - profile?: ToolProfileId, -): { allow?: string[]; deny?: string[] } | undefined { - if (!profile) return undefined; - const resolved = TOOL_PROFILES[profile]; - if (!resolved) return undefined; - if (!resolved.allow && !resolved.deny) return undefined; - const result: { allow?: string[]; deny?: string[] } = {}; - if (resolved.allow) { - result.allow = [...resolved.allow]; - } - if (resolved.deny) { - result.deny = [...resolved.deny]; - } - return result; -} diff --git a/src/agent/tools/index.ts b/src/agent/tools/index.ts index 1e6f6334..70b616f4 100644 --- a/src/agent/tools/index.ts +++ b/src/agent/tools/index.ts @@ -8,17 +8,14 @@ export { createProcessTool } from "./process.js"; export { createGlobTool } from "./glob.js"; export { createWebFetchTool, createWebSearchTool } from "./web/index.js"; -// Tool groups and profiles +// Tool groups export { - type ToolProfileId, TOOL_NAME_ALIASES, TOOL_GROUPS, - TOOL_PROFILES, DEFAULT_SUBAGENT_TOOL_DENY, normalizeToolName, normalizeToolList, expandToolGroups, - getProfilePolicy, } from "./groups.js"; // Tool policy system diff --git a/src/agent/tools/policy.test.ts b/src/agent/tools/policy.test.ts index e0902708..3cfa5276 100644 --- a/src/agent/tools/policy.test.ts +++ b/src/agent/tools/policy.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; import { filterTools } from "./policy.js"; -import { TOOL_PROFILES, expandToolGroups } from "./groups.js"; +import { expandToolGroups } from "./groups.js"; // Mock tools for testing const mockTools = [ @@ -36,58 +36,12 @@ describe("tool groups", () => { }); }); -describe("tool profiles", () => { - it("minimal has empty allow", () => { - expect(TOOL_PROFILES.minimal.allow).toEqual([]); - }); - - it("coding has fs and runtime", () => { - expect(TOOL_PROFILES.coding.allow).toEqual(["group:fs", "group:runtime"]); - }); - - it("full has no restrictions", () => { - expect(TOOL_PROFILES.full.allow).toBeUndefined(); - expect(TOOL_PROFILES.full.deny).toBeUndefined(); - }); -}); - describe("filterTools", () => { it("no config returns all tools", () => { const filtered = filterTools(mockTools, {}); expect(filtered.length).toBe(mockTools.length); }); - it("minimal profile returns no tools", () => { - const filtered = filterTools(mockTools, { config: { profile: "minimal" } }); - expect(filtered.length).toBe(0); - }); - - it("coding profile returns fs and runtime", () => { - const filtered = filterTools(mockTools, { config: { profile: "coding" } }); - const names = filtered.map((t) => t.name).sort(); - expect(names).toEqual(["edit", "exec", "glob", "process", "read", "write"]); - }); - - it("web profile returns all", () => { - const filtered = filterTools(mockTools, { config: { profile: "web" } }); - const names = filtered.map((t) => t.name).sort(); - expect(names).toEqual([ - "edit", - "exec", - "glob", - "process", - "read", - "web_fetch", - "web_search", - "write", - ]); - }); - - it("full profile returns all tools", () => { - const filtered = filterTools(mockTools, { config: { profile: "full" } }); - expect(filtered.length).toBe(mockTools.length); - }); - it("deny specific tool", () => { const filtered = filterTools(mockTools, { config: { deny: ["exec"] } }); const names = filtered.map((t) => t.name); @@ -110,6 +64,22 @@ describe("filterTools", () => { const names = filtered.map((t) => t.name).sort(); expect(names).toEqual(["read", "write"]); }); + + it("allow with group:* syntax", () => { + const filtered = filterTools(mockTools, { + config: { allow: ["group:fs", "group:runtime"] }, + }); + const names = filtered.map((t) => t.name).sort(); + expect(names).toEqual(["edit", "exec", "glob", "process", "read", "write"]); + }); + + it("deny with group:* syntax", () => { + const filtered = filterTools(mockTools, { + config: { deny: ["group:web"] }, + }); + const names = filtered.map((t) => t.name).sort(); + expect(names).toEqual(["edit", "exec", "glob", "process", "read", "write"]); + }); }); describe("provider-specific filtering", () => { @@ -149,10 +119,10 @@ describe("subagent restrictions", () => { }); describe("combined filtering", () => { - it("profile + deny", () => { + it("allow + deny", () => { const filtered = filterTools(mockTools, { config: { - profile: "coding", + allow: ["group:fs", "group:runtime"], deny: ["exec"], }, }); @@ -160,10 +130,10 @@ describe("combined filtering", () => { expect(names).toEqual(["edit", "glob", "process", "read", "write"]); }); - it("profile + provider deny", () => { + it("allow + provider deny", () => { const filtered = filterTools(mockTools, { config: { - profile: "web", + allow: ["group:fs", "group:runtime", "group:web"], byProvider: { google: { deny: ["exec"] }, }, diff --git a/src/agent/tools/policy.ts b/src/agent/tools/policy.ts index 5b8c2fc0..3e1468fe 100644 --- a/src/agent/tools/policy.ts +++ b/src/agent/tools/policy.ts @@ -1,18 +1,15 @@ /** * Tool policy system for filtering tools based on configuration. * - * Supports 4 layers of filtering: - * 1. Profile - base tool set (minimal/coding/web/full) - * 2. Global allow/deny - user customization - * 3. Provider-specific - different rules for different LLM providers - * 4. Subagent restrictions - limited tools for spawned agents + * Supports 3 layers of filtering: + * 1. Global allow/deny - user customization + * 2. Provider-specific - different rules for different LLM providers + * 3. Subagent restrictions - limited tools for spawned agents */ import type { AgentTool } from "@mariozechner/pi-agent-core"; import { - type ToolProfileId, expandToolGroups, - getProfilePolicy, normalizeToolName, DEFAULT_SUBAGENT_TOOL_DENY, } from "./groups.js"; @@ -31,11 +28,9 @@ export interface ToolPolicy { * Full tool configuration from config file. */ export interface ToolsConfig { - /** Base profile (minimal/coding/web/full) */ - profile?: ToolProfileId; - /** Additional tools to allow */ + /** Tools to allow (supports group:* syntax) */ allow?: string[]; - /** Tools to deny */ + /** Tools to deny (takes precedence over allow) */ deny?: string[]; /** Provider-specific overrides */ byProvider?: Record; @@ -191,12 +186,11 @@ export interface FilterToolsOptions { } /** - * Filter tools through the 4-layer policy system. + * Filter tools through the 3-layer policy system. * - * Layer 1: Profile (base tool set) - * Layer 2: Global allow/deny - * Layer 3: Provider-specific - * Layer 4: Subagent restrictions + * Layer 1: Global allow/deny + * Layer 2: Provider-specific + * Layer 3: Subagent restrictions */ export function filterTools( tools: AgentTool[], @@ -206,15 +200,7 @@ export function filterTools( let filtered = tools; - // Layer 1: Profile - if (config?.profile) { - const profilePolicy = getProfilePolicy(config.profile); - if (profilePolicy) { - filtered = filterToolsByPolicy(filtered, profilePolicy); - } - } - - // Layer 2: Global allow/deny + // Layer 1: Global allow/deny if (config?.allow || config?.deny) { const globalPolicy: ToolPolicy = {}; if (config.allow) { @@ -226,7 +212,7 @@ export function filterTools( filtered = filterToolsByPolicy(filtered, globalPolicy); } - // Layer 3: Provider-specific + // Layer 2: Provider-specific if (provider && config?.byProvider) { const providerPolicy = resolveProviderPolicy(config.byProvider, provider); if (providerPolicy) { @@ -234,7 +220,7 @@ export function filterTools( } } - // Layer 4: Subagent restrictions + // Layer 3: Subagent restrictions if (isSubagent) { const subagentPolicy = getSubagentPolicy(); filtered = filterToolsByPolicy(filtered, subagentPolicy); @@ -246,7 +232,6 @@ export function filterTools( /** * Merge two ToolsConfig objects. * The override config takes precedence: - * - profile: override wins if set * - allow: union of both * - deny: union of both * - byProvider: deep merge with override taking precedence @@ -261,12 +246,6 @@ export function mergeToolsConfig( const result: ToolsConfig = {}; - // profile: override wins - const profile = override.profile ?? base.profile; - if (profile) { - result.profile = profile; - } - // allow: union const allow = mergeAllow(base.allow, override.allow); if (allow) { @@ -321,15 +300,7 @@ export function wouldToolBeAllowed( ): boolean { const { config, provider, isSubagent } = options; - // Layer 1: Profile - if (config?.profile) { - const profilePolicy = getProfilePolicy(config.profile); - if (profilePolicy && !isToolAllowed(toolName, profilePolicy)) { - return false; - } - } - - // Layer 2: Global allow/deny + // Layer 1: Global allow/deny if (config?.allow || config?.deny) { const globalPolicy: ToolPolicy = {}; if (config.allow) { @@ -343,7 +314,7 @@ export function wouldToolBeAllowed( } } - // Layer 3: Provider-specific + // Layer 2: Provider-specific if (provider && config?.byProvider) { const providerPolicy = resolveProviderPolicy(config.byProvider, provider); if (providerPolicy && !isToolAllowed(toolName, providerPolicy)) { @@ -351,7 +322,7 @@ export function wouldToolBeAllowed( } } - // Layer 4: Subagent restrictions + // Layer 3: Subagent restrictions if (isSubagent) { const subagentPolicy = getSubagentPolicy(); if (!isToolAllowed(toolName, subagentPolicy)) {