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 <noreply@anthropic.com>
This commit is contained in:
Jiang Bohan 2026-02-05 02:58:15 +08:00
parent 9b16001e0e
commit 087d1a8653
6 changed files with 128 additions and 361 deletions

View file

@ -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

View file

@ -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** - 可视化输出生成

View file

@ -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<string, string[]> = {
],
};
/**
* Tool profiles - predefined tool sets.
*/
export const TOOL_PROFILES: Record<ToolProfileId, { allow?: string[]; deny?: string[] }> = {
// 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;
}

View file

@ -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

View file

@ -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"] },
},

View file

@ -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<string, ToolPolicy>;
@ -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<any>[],
@ -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)) {