server/coder: working-tree backend changes (pre-existing)
Checkpoint of in-progress backend work present in the tree, not authored this session: auto_name, inference tool-phase/turn, secret_guard, provider-registry, plus a new agent-allowlist test (7 tests, passing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
107
apps/server/src/services/__tests__/agent-allowlist.test.ts
Normal file
107
apps/server/src/services/__tests__/agent-allowlist.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseAgentsMd, matchToolGlob } from '../agents.js';
|
||||
import { toolJsonSchemas } from '../tools.js';
|
||||
|
||||
describe('agent tool allowlist', () => {
|
||||
const plannerMd = `# Agents
|
||||
|
||||
## Planner
|
||||
---
|
||||
temperature: 0.6
|
||||
tools: [view_file, grep, list_dir, find_files]
|
||||
description: Read-only planner
|
||||
---
|
||||
You plan.
|
||||
`;
|
||||
|
||||
it('parses an agent with a restricted tool allowlist', () => {
|
||||
const { agents, errors } = parseAgentsMd(plannerMd);
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(agents).toHaveLength(1);
|
||||
const planner = agents[0]!;
|
||||
expect(planner.name).toBe('Planner');
|
||||
expect(planner.tools).toEqual(['view_file', 'grep', 'list_dir', 'find_files']);
|
||||
});
|
||||
|
||||
it('stream-phase filter: agent allowlist excludes tools not in the list', () => {
|
||||
const { agents } = parseAgentsMd(plannerMd);
|
||||
const planner = agents[0]!;
|
||||
const allSchemas = toolJsonSchemas();
|
||||
const filtered = allSchemas.filter((t) =>
|
||||
matchToolGlob(t.function.name, planner.tools),
|
||||
);
|
||||
const filteredNames = filtered.map((t) => t.function.name);
|
||||
expect(filteredNames).toContain('view_file');
|
||||
expect(filteredNames).toContain('grep');
|
||||
expect(filteredNames).not.toContain('edit_file');
|
||||
expect(filteredNames).not.toContain('web_search');
|
||||
expect(filteredNames).not.toContain('get_codebase_overview');
|
||||
expect(filtered).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('tool-phase guard: rejects tool call not in agent allowlist', () => {
|
||||
const { agents } = parseAgentsMd(plannerMd);
|
||||
const planner = agents[0]!;
|
||||
expect(matchToolGlob('edit_file', planner.tools)).toBe(false);
|
||||
expect(matchToolGlob('create_file', planner.tools)).toBe(false);
|
||||
expect(matchToolGlob('delete_file', planner.tools)).toBe(false);
|
||||
expect(matchToolGlob('web_search', planner.tools)).toBe(false);
|
||||
});
|
||||
|
||||
it('tool-phase guard: allows tool call in agent allowlist', () => {
|
||||
const { agents } = parseAgentsMd(plannerMd);
|
||||
const planner = agents[0]!;
|
||||
expect(matchToolGlob('view_file', planner.tools)).toBe(true);
|
||||
expect(matchToolGlob('grep', planner.tools)).toBe(true);
|
||||
expect(matchToolGlob('list_dir', planner.tools)).toBe(true);
|
||||
expect(matchToolGlob('find_files', planner.tools)).toBe(true);
|
||||
});
|
||||
|
||||
it('null/absent tools field defaults to all tools (no regression)', () => {
|
||||
const noToolsMd = `# Agents
|
||||
|
||||
## Default
|
||||
---
|
||||
temperature: 0.7
|
||||
description: Uses all tools
|
||||
---
|
||||
Default agent.
|
||||
`;
|
||||
const { agents } = parseAgentsMd(noToolsMd);
|
||||
const agent = agents[0]!;
|
||||
const allSchemas = toolJsonSchemas();
|
||||
const filtered = allSchemas.filter((t) =>
|
||||
matchToolGlob(t.function.name, agent.tools),
|
||||
);
|
||||
expect(filtered.length).toBe(allSchemas.length);
|
||||
});
|
||||
|
||||
it('builder agent: write tools filtered out when not in ALL_TOOLS (BooChat context)', () => {
|
||||
const builderMd = `# Agents
|
||||
|
||||
## Builder
|
||||
---
|
||||
temperature: 0.6
|
||||
tools: [view_file, grep, list_dir, find_files, edit_file, create_file, delete_file, apply_pending, rewind]
|
||||
description: Read and write tools
|
||||
---
|
||||
You build.
|
||||
`;
|
||||
const { agents } = parseAgentsMd(builderMd);
|
||||
const builder = agents[0]!;
|
||||
expect(matchToolGlob('view_file', builder.tools)).toBe(true);
|
||||
expect(matchToolGlob('grep', builder.tools)).toBe(true);
|
||||
// Write tools not in server's ALL_TOOLS are silently filtered during parsing.
|
||||
// In BooCoder context (where ALL_TOOLS includes write tools), they'd be retained.
|
||||
expect(builder.tools).not.toContain('edit_file');
|
||||
expect(builder.tools).not.toContain('create_file');
|
||||
expect(matchToolGlob('web_search', builder.tools)).toBe(false);
|
||||
});
|
||||
|
||||
it('matchToolGlob rejects hallucinated tool against exact allowlist', () => {
|
||||
const allowlist = ['view_file', 'grep', 'list_dir'];
|
||||
expect(matchToolGlob('edit_file', allowlist)).toBe(false);
|
||||
expect(matchToolGlob('rm_rf', allowlist)).toBe(false);
|
||||
expect(matchToolGlob('view_file_extended', allowlist)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user