feat: add tool allow/deny profiles with per-agent and per-provider filtering

Implements configurable tool filtering with four built-in profiles
(minimal, messaging, coding, full), global and per-agent/per-provider
allow/deny lists with glob pattern support, and defense-in-depth
enforcement at both tool listing and execution time.

New: src/tools/policy.ts (ToolPolicy engine), src/tools/policy.test.ts (37 tests)
Modified: config schema, tool registry, tool executor, NativeAgent,
AgentOrchestrator, daemon wiring, gateway tool handler, test mocks
This commit is contained in:
William Valentin
2026-02-06 15:30:34 -08:00
parent 8238d3e981
commit ee0af0cc06
13 changed files with 794 additions and 8 deletions
+15 -2
View File
@@ -4,6 +4,7 @@ import type { Session } from '../../session/index.js';
import type { ToolRegistry } from '../../tools/registry.js';
import type { ToolExecutor } from '../../tools/executor.js';
import type { ToolResult } from '../../tools/types.js';
import type { ToolPolicyContext } from '../../tools/policy.js';
export interface ToolUseEvent {
type: 'start' | 'end';
@@ -20,6 +21,8 @@ export interface NativeAgentConfig {
toolExecutor?: ToolExecutor;
maxIterations?: number;
onToolUse?: (event: ToolUseEvent) => void;
/** Policy context for tool filtering (agent tier, provider). */
toolPolicyContext?: ToolPolicyContext;
}
// Internal message type for the tool loop — supports both text and structured content blocks.
@@ -41,6 +44,7 @@ export class NativeAgent {
private onToolUse?: (event: ToolUseEvent) => void;
private _totalUsage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
private _callCount: number = 0;
private _toolPolicyContext?: ToolPolicyContext;
constructor(config: NativeAgentConfig) {
this.modelClient = config.modelClient;
@@ -50,6 +54,7 @@ export class NativeAgent {
this.toolExecutor = config.toolExecutor;
this.maxIterations = config.maxIterations ?? 10;
this.onToolUse = config.onToolUse;
this._toolPolicyContext = config.toolPolicyContext;
}
private get history(): Message[] {
@@ -96,7 +101,7 @@ export class NativeAgent {
}
private async toolLoop(): Promise<string> {
const tools = this.toolRegistry!.toAnthropicFormat();
const tools = this.toolRegistry!.filteredToAnthropicFormat(this._toolPolicyContext);
// Build the loop messages from existing history.
// These are the messages sent to the model, including any structured tool blocks.
@@ -151,7 +156,7 @@ export class NativeAgent {
for (const tc of response.toolCalls) {
this.onToolUse?.({ type: 'start', tool: tc.name, args: tc.args });
const result = await this.toolExecutor!.execute(tc.name, tc.args);
const result = await this.toolExecutor!.execute(tc.name, tc.args, this._toolPolicyContext);
this.onToolUse?.({ type: 'end', tool: tc.name, result });
@@ -226,4 +231,12 @@ export class NativeAgent {
setOnToolUse(callback: ((event: ToolUseEvent) => void) | undefined): void {
this.onToolUse = callback;
}
setToolPolicyContext(context: ToolPolicyContext | undefined): void {
this._toolPolicyContext = context;
}
getToolPolicyContext(): ToolPolicyContext | undefined {
return this._toolPolicyContext;
}
}