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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { MemoryStore } from '../../memory/store.js';
|
||||
import type { ToolPolicyContext } from '../../tools/policy.js';
|
||||
import { NativeAgent } from './agent.js';
|
||||
import type { ToolUseEvent } from './agent.js';
|
||||
import { shouldCompact } from '../../context/tokens.js';
|
||||
@@ -87,6 +88,8 @@ export interface OrchestratorConfig {
|
||||
contextWindow?: number;
|
||||
/** Optional memory store for injecting persistent memory into the system prompt. */
|
||||
memoryStore?: MemoryStore;
|
||||
/** Policy context for tool filtering (agent tier, provider). */
|
||||
toolPolicyContext?: ToolPolicyContext;
|
||||
}
|
||||
|
||||
// ── AgentOrchestrator ─────────────────────────────────────────────────
|
||||
@@ -134,6 +137,7 @@ export class AgentOrchestrator {
|
||||
toolExecutor: config.toolExecutor,
|
||||
maxIterations: config.maxIterations,
|
||||
onToolUse: config.onToolUse,
|
||||
toolPolicyContext: config.toolPolicyContext,
|
||||
});
|
||||
|
||||
// Set the primary tier on the agent
|
||||
@@ -174,9 +178,10 @@ export class AgentOrchestrator {
|
||||
maxTokens: request.maxTokens,
|
||||
};
|
||||
|
||||
// Optionally include tools from the registry
|
||||
// Optionally include tools from the registry (filtered by policy)
|
||||
if (request.tools && this._toolRegistry) {
|
||||
chatRequest.tools = this._toolRegistry.toAnthropicFormat();
|
||||
const policyContext = this._agent.getToolPolicyContext();
|
||||
chatRequest.tools = this._toolRegistry.filteredToAnthropicFormat(policyContext);
|
||||
}
|
||||
|
||||
const response = await this._modelRouter.chat(chatRequest, tier);
|
||||
|
||||
Reference in New Issue
Block a user