mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-28 23:48:39 +02:00
feat: add tool-specific approval overrides
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,28 +1,33 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import {
|
||||
approvalPolicyRequiresToolCallApproval,
|
||||
approvalPolicyRequiresCheckpoint,
|
||||
normalizeApprovalPolicy,
|
||||
normalizePendingApproval,
|
||||
normalizePendingApprovalState,
|
||||
normalizeSessionApprovalSettings,
|
||||
dequeuePendingApprovalState,
|
||||
enqueuePendingApprovalState,
|
||||
listPendingApprovals,
|
||||
approvalPolicyRequiresCheckpoint,
|
||||
normalizePendingApprovalState,
|
||||
normalizeApprovalPolicy,
|
||||
normalizePendingApproval,
|
||||
resolveEffectiveApprovalPolicy,
|
||||
} from '@shared/domain/approval';
|
||||
|
||||
describe('approval helpers', () => {
|
||||
test('normalizes duplicate checkpoint rules into stable agent-scoped policy entries', () => {
|
||||
test('normalizes duplicate checkpoint rules and auto-approved tools into stable policy entries', () => {
|
||||
expect(normalizeApprovalPolicy({
|
||||
rules: [
|
||||
{ kind: 'tool-call', agentIds: ['agent-1', ' agent-1 ', 'agent-2'] },
|
||||
{ kind: 'tool-call', agentIds: ['agent-2', 'agent-3'] },
|
||||
{ kind: 'final-response', agentIds: [] },
|
||||
],
|
||||
autoApprovedToolNames: [' git.status ', 'git.status', 'lsp_ts_hover'],
|
||||
})).toEqual({
|
||||
rules: [
|
||||
{ kind: 'tool-call', agentIds: ['agent-1', 'agent-2', 'agent-3'] },
|
||||
{ kind: 'final-response' },
|
||||
],
|
||||
autoApprovedToolNames: ['git.status', 'lsp_ts_hover'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,11 +37,42 @@ describe('approval helpers', () => {
|
||||
{ kind: 'tool-call', agentIds: ['agent-1'] },
|
||||
{ kind: 'final-response', agentIds: [] },
|
||||
],
|
||||
autoApprovedToolNames: ['git.status'],
|
||||
});
|
||||
|
||||
expect(approvalPolicyRequiresCheckpoint(policy, 'tool-call', 'agent-1')).toBe(true);
|
||||
expect(approvalPolicyRequiresCheckpoint(policy, 'tool-call', 'agent-2')).toBe(false);
|
||||
expect(approvalPolicyRequiresCheckpoint(policy, 'final-response', 'agent-2')).toBe(true);
|
||||
expect(approvalPolicyRequiresToolCallApproval(policy, 'agent-1', 'git.status')).toBe(false);
|
||||
expect(approvalPolicyRequiresToolCallApproval(policy, 'agent-1', 'git.diff')).toBe(true);
|
||||
expect(approvalPolicyRequiresToolCallApproval(policy, 'agent-2', 'git.diff')).toBe(false);
|
||||
});
|
||||
|
||||
test('resolves session approval settings over pattern auto-approval defaults', () => {
|
||||
expect(resolveEffectiveApprovalPolicy(
|
||||
{
|
||||
rules: [{ kind: 'tool-call' }],
|
||||
autoApprovedToolNames: ['git.status'],
|
||||
},
|
||||
normalizeSessionApprovalSettings({
|
||||
autoApprovedToolNames: ['git.diff'],
|
||||
}),
|
||||
)).toEqual({
|
||||
rules: [{ kind: 'tool-call' }],
|
||||
autoApprovedToolNames: ['git.diff'],
|
||||
});
|
||||
|
||||
expect(resolveEffectiveApprovalPolicy(
|
||||
{
|
||||
rules: [{ kind: 'tool-call' }],
|
||||
autoApprovedToolNames: ['git.status'],
|
||||
},
|
||||
normalizeSessionApprovalSettings({
|
||||
autoApprovedToolNames: [],
|
||||
}),
|
||||
)).toEqual({
|
||||
rules: [{ kind: 'tool-call' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizes pending approvals with optional message previews', () => {
|
||||
|
||||
@@ -125,4 +125,24 @@ describe('pattern validation', () => {
|
||||
'Approval checkpoint "tool-call" references unknown agent "agent-missing".',
|
||||
);
|
||||
});
|
||||
|
||||
test('approval policy rejects unknown auto-approved tool references when tool names are provided', () => {
|
||||
const singlePattern = createBuiltinPatterns(BUILTIN_TIMESTAMP).find(
|
||||
(pattern) => pattern.mode === 'single',
|
||||
);
|
||||
|
||||
expect(singlePattern).toBeDefined();
|
||||
|
||||
const issues = validatePatternDefinition({
|
||||
...singlePattern!,
|
||||
approvalPolicy: {
|
||||
rules: [{ kind: 'tool-call' }],
|
||||
autoApprovedToolNames: ['git.status', 'unknown.tool'],
|
||||
},
|
||||
}, ['git.status']);
|
||||
|
||||
expect(issues.find((issue) => issue.field === 'approvalPolicy')?.message).toBe(
|
||||
'Approval auto-approve references unknown tool "unknown.tool".',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,10 @@ import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import type { PatternDefinition } from '@shared/domain/pattern';
|
||||
import {
|
||||
applySessionApprovalSettings,
|
||||
applyScratchpadSessionConfig,
|
||||
createScratchpadSessionConfig,
|
||||
resolveSessionApprovalSettings,
|
||||
resolveSessionToolingSelection,
|
||||
resolveSessionTitle,
|
||||
resolveScratchpadSessionConfig,
|
||||
@@ -143,3 +145,30 @@ describe('session tooling helpers', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('session approval helpers', () => {
|
||||
test('normalizes session approval overrides and applies them over pattern defaults', () => {
|
||||
const pattern = {
|
||||
...createPattern(),
|
||||
approvalPolicy: {
|
||||
rules: [{ kind: 'tool-call' as const }],
|
||||
autoApprovedToolNames: ['git.status'],
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveSessionApprovalSettings(createSession())).toBeUndefined();
|
||||
expect(
|
||||
applySessionApprovalSettings(
|
||||
pattern,
|
||||
createSession({
|
||||
approvalSettings: {
|
||||
autoApprovedToolNames: ['git.diff', ' git.diff '],
|
||||
},
|
||||
}),
|
||||
).approvalPolicy,
|
||||
).toEqual({
|
||||
rules: [{ kind: 'tool-call' }],
|
||||
autoApprovedToolNames: ['git.diff'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,6 +122,9 @@ describe('session library helpers', () => {
|
||||
isPinned: true,
|
||||
isArchived: true,
|
||||
lastError: 'sidecar crashed',
|
||||
approvalSettings: {
|
||||
autoApprovedToolNames: ['git.status'],
|
||||
},
|
||||
pendingApproval: {
|
||||
id: 'approval-1',
|
||||
kind: 'tool-call',
|
||||
@@ -165,6 +168,9 @@ describe('session library helpers', () => {
|
||||
updatedAt: '2026-03-23T00:07:00.000Z',
|
||||
});
|
||||
expect(session.messages[0]?.pending).toBe(false);
|
||||
expect(session.approvalSettings).toEqual({
|
||||
autoApprovedToolNames: ['git.status'],
|
||||
});
|
||||
expect(session.pendingApproval).toBeUndefined();
|
||||
expect(session.pendingApprovalQueue).toBeUndefined();
|
||||
expect(session.runs).toEqual([]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import {
|
||||
listApprovalToolDefinitions,
|
||||
normalizeWorkspaceSettings,
|
||||
validateLspProfileDefinition,
|
||||
validateMcpServerDefinition,
|
||||
@@ -155,4 +156,58 @@ describe('tooling settings helpers', () => {
|
||||
}),
|
||||
).toBe('LSP profile "Typescript LSP" needs the "--stdio" argument.');
|
||||
});
|
||||
|
||||
test('lists approval tools from MCP and LSP definitions using runtime tool identifiers', () => {
|
||||
const tools = listApprovalToolDefinitions({
|
||||
mcpServers: [
|
||||
{
|
||||
id: 'mcp-git',
|
||||
name: 'Git MCP',
|
||||
transport: 'local',
|
||||
command: 'node',
|
||||
args: ['server.js'],
|
||||
tools: ['git.status', 'git.diff'],
|
||||
createdAt: TIMESTAMP,
|
||||
updatedAt: TIMESTAMP,
|
||||
},
|
||||
{
|
||||
id: 'mcp-extra',
|
||||
name: 'Extra MCP',
|
||||
transport: 'local',
|
||||
command: 'node',
|
||||
args: ['extra.js'],
|
||||
tools: ['git.status'],
|
||||
createdAt: TIMESTAMP,
|
||||
updatedAt: TIMESTAMP,
|
||||
},
|
||||
],
|
||||
lspProfiles: [
|
||||
{
|
||||
id: 'ts',
|
||||
name: 'TypeScript',
|
||||
command: 'typescript-language-server',
|
||||
args: ['--stdio'],
|
||||
languageId: 'typescript',
|
||||
fileExtensions: ['.ts', '.tsx'],
|
||||
createdAt: TIMESTAMP,
|
||||
updatedAt: TIMESTAMP,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(tools).toContainEqual({
|
||||
id: 'git.status',
|
||||
label: 'git.status',
|
||||
kind: 'mcp',
|
||||
providerIds: ['mcp-git', 'mcp-extra'],
|
||||
providerNames: ['Git MCP', 'Extra MCP'],
|
||||
});
|
||||
expect(tools).toContainEqual({
|
||||
id: 'lsp_ts_hover',
|
||||
label: 'TypeScript · Hover',
|
||||
kind: 'lsp',
|
||||
providerIds: ['ts'],
|
||||
providerNames: ['TypeScript'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user