@build-astron-co/nimbus 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three-Mode System Tests
|
|
3
|
+
*
|
|
4
|
+
* Validates the plan / build / deploy mode system, including tool filtering,
|
|
5
|
+
* mode cycling, mode state management, and mode metadata (labels, colors).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect } from 'bun:test';
|
|
9
|
+
import {
|
|
10
|
+
getToolsForMode,
|
|
11
|
+
cycleMode,
|
|
12
|
+
getModes,
|
|
13
|
+
createModeState,
|
|
14
|
+
switchMode,
|
|
15
|
+
isToolAllowedInMode,
|
|
16
|
+
getModeLabel,
|
|
17
|
+
getModeColor,
|
|
18
|
+
MODE_CONFIGS,
|
|
19
|
+
} from '../agent/modes';
|
|
20
|
+
|
|
21
|
+
// ===========================================================================
|
|
22
|
+
// getToolsForMode
|
|
23
|
+
// ===========================================================================
|
|
24
|
+
|
|
25
|
+
describe('getToolsForMode', () => {
|
|
26
|
+
// -----------------------------------------------------------------------
|
|
27
|
+
// Plan mode
|
|
28
|
+
// -----------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
describe('plan mode', () => {
|
|
31
|
+
const planToolNames = getToolsForMode('plan').map(t => t.name);
|
|
32
|
+
|
|
33
|
+
test('returns only read-only tools', () => {
|
|
34
|
+
const expected = [
|
|
35
|
+
'read_file',
|
|
36
|
+
'glob',
|
|
37
|
+
'grep',
|
|
38
|
+
'list_dir',
|
|
39
|
+
'webfetch',
|
|
40
|
+
'cost_estimate',
|
|
41
|
+
'drift_detect',
|
|
42
|
+
'todo_read',
|
|
43
|
+
'todo_write',
|
|
44
|
+
'cloud_discover',
|
|
45
|
+
];
|
|
46
|
+
for (const name of expected) {
|
|
47
|
+
expect(planToolNames).toContain(name);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('does NOT include edit_file', () => {
|
|
52
|
+
expect(planToolNames).not.toContain('edit_file');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('does NOT include write_file', () => {
|
|
56
|
+
expect(planToolNames).not.toContain('write_file');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('does NOT include bash', () => {
|
|
60
|
+
expect(planToolNames).not.toContain('bash');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('does NOT include terraform', () => {
|
|
64
|
+
expect(planToolNames).not.toContain('terraform');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('does NOT include kubectl', () => {
|
|
68
|
+
expect(planToolNames).not.toContain('kubectl');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('does NOT include helm', () => {
|
|
72
|
+
expect(planToolNames).not.toContain('helm');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// -----------------------------------------------------------------------
|
|
77
|
+
// Build mode
|
|
78
|
+
// -----------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
describe('build mode', () => {
|
|
81
|
+
const buildToolNames = getToolsForMode('build').map(t => t.name);
|
|
82
|
+
|
|
83
|
+
test('includes editing tools (edit_file, multi_edit, write_file, bash)', () => {
|
|
84
|
+
expect(buildToolNames).toContain('edit_file');
|
|
85
|
+
expect(buildToolNames).toContain('multi_edit');
|
|
86
|
+
expect(buildToolNames).toContain('write_file');
|
|
87
|
+
expect(buildToolNames).toContain('bash');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('includes terraform, kubectl, helm (restricted by permissions not mode)', () => {
|
|
91
|
+
expect(buildToolNames).toContain('terraform');
|
|
92
|
+
expect(buildToolNames).toContain('kubectl');
|
|
93
|
+
expect(buildToolNames).toContain('helm');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('includes all plan mode tools as well', () => {
|
|
97
|
+
const planToolNames = getToolsForMode('plan').map(t => t.name);
|
|
98
|
+
for (const name of planToolNames) {
|
|
99
|
+
expect(buildToolNames).toContain(name);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// -----------------------------------------------------------------------
|
|
105
|
+
// Deploy mode
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
describe('deploy mode', () => {
|
|
109
|
+
const deployTools = getToolsForMode('deploy');
|
|
110
|
+
const deployToolNames = deployTools.map(t => t.name);
|
|
111
|
+
|
|
112
|
+
test('includes all tools', () => {
|
|
113
|
+
// Deploy mode should include every tool that exists across standard and devops
|
|
114
|
+
const buildToolNames = getToolsForMode('build').map(t => t.name);
|
|
115
|
+
for (const name of buildToolNames) {
|
|
116
|
+
expect(deployToolNames).toContain(name);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('returns a non-empty array', () => {
|
|
121
|
+
expect(deployTools.length).toBeGreaterThan(0);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
// cycleMode
|
|
128
|
+
// ===========================================================================
|
|
129
|
+
|
|
130
|
+
describe('cycleMode', () => {
|
|
131
|
+
test('plan cycles to build', () => {
|
|
132
|
+
expect(cycleMode('plan')).toBe('build');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('build cycles to deploy', () => {
|
|
136
|
+
expect(cycleMode('build')).toBe('deploy');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('deploy cycles back to plan', () => {
|
|
140
|
+
expect(cycleMode('deploy')).toBe('plan');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ===========================================================================
|
|
145
|
+
// getModes
|
|
146
|
+
// ===========================================================================
|
|
147
|
+
|
|
148
|
+
describe('getModes', () => {
|
|
149
|
+
test('returns ["plan", "build", "deploy"]', () => {
|
|
150
|
+
expect(getModes()).toEqual(['plan', 'build', 'deploy']);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ===========================================================================
|
|
155
|
+
// createModeState
|
|
156
|
+
// ===========================================================================
|
|
157
|
+
|
|
158
|
+
describe('createModeState', () => {
|
|
159
|
+
test('defaults to plan mode', () => {
|
|
160
|
+
const state = createModeState();
|
|
161
|
+
expect(state.current).toBe('plan');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('can start in deploy mode', () => {
|
|
165
|
+
const state = createModeState('deploy');
|
|
166
|
+
expect(state.current).toBe('deploy');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('can start in build mode', () => {
|
|
170
|
+
const state = createModeState('build');
|
|
171
|
+
expect(state.current).toBe('build');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('initializes with empty permission state', () => {
|
|
175
|
+
const state = createModeState();
|
|
176
|
+
expect(state.permissionState.approvedTools.size).toBe(0);
|
|
177
|
+
expect(state.permissionState.approvedActions.size).toBe(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ===========================================================================
|
|
182
|
+
// switchMode
|
|
183
|
+
// ===========================================================================
|
|
184
|
+
|
|
185
|
+
describe('switchMode', () => {
|
|
186
|
+
test('resets permission state (approvedTools cleared)', () => {
|
|
187
|
+
let state = createModeState('plan');
|
|
188
|
+
// Simulate approving a tool in plan mode
|
|
189
|
+
state.permissionState.approvedTools.add('read_file');
|
|
190
|
+
state.permissionState.approvedActions.add('terraform:plan');
|
|
191
|
+
expect(state.permissionState.approvedTools.size).toBe(1);
|
|
192
|
+
expect(state.permissionState.approvedActions.size).toBe(1);
|
|
193
|
+
|
|
194
|
+
// Switch to build mode
|
|
195
|
+
state = switchMode(state, 'build');
|
|
196
|
+
|
|
197
|
+
expect(state.current).toBe('build');
|
|
198
|
+
expect(state.permissionState.approvedTools.size).toBe(0);
|
|
199
|
+
expect(state.permissionState.approvedActions.size).toBe(0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('updates the current mode', () => {
|
|
203
|
+
let state = createModeState('plan');
|
|
204
|
+
state = switchMode(state, 'deploy');
|
|
205
|
+
expect(state.current).toBe('deploy');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ===========================================================================
|
|
210
|
+
// isToolAllowedInMode
|
|
211
|
+
// ===========================================================================
|
|
212
|
+
|
|
213
|
+
describe('isToolAllowedInMode', () => {
|
|
214
|
+
test('read_file is allowed in plan', () => {
|
|
215
|
+
expect(isToolAllowedInMode('read_file', 'plan')).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('edit_file is NOT allowed in plan', () => {
|
|
219
|
+
expect(isToolAllowedInMode('edit_file', 'plan')).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('edit_file is allowed in build', () => {
|
|
223
|
+
expect(isToolAllowedInMode('edit_file', 'build')).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('terraform is allowed in deploy', () => {
|
|
227
|
+
expect(isToolAllowedInMode('terraform', 'deploy')).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('terraform is allowed in build (restricted by permissions, not mode)', () => {
|
|
231
|
+
expect(isToolAllowedInMode('terraform', 'build')).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('glob is allowed in plan', () => {
|
|
235
|
+
expect(isToolAllowedInMode('glob', 'plan')).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('bash is NOT allowed in plan', () => {
|
|
239
|
+
expect(isToolAllowedInMode('bash', 'plan')).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('bash is allowed in build', () => {
|
|
243
|
+
expect(isToolAllowedInMode('bash', 'build')).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('write_file is NOT allowed in plan', () => {
|
|
247
|
+
expect(isToolAllowedInMode('write_file', 'plan')).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('write_file is allowed in build', () => {
|
|
251
|
+
expect(isToolAllowedInMode('write_file', 'build')).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('cloud_discover is allowed in plan', () => {
|
|
255
|
+
expect(isToolAllowedInMode('cloud_discover', 'plan')).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('cost_estimate is allowed in plan', () => {
|
|
259
|
+
expect(isToolAllowedInMode('cost_estimate', 'plan')).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// ===========================================================================
|
|
264
|
+
// getModeLabel
|
|
265
|
+
// ===========================================================================
|
|
266
|
+
|
|
267
|
+
describe('getModeLabel', () => {
|
|
268
|
+
test('plan returns "Plan"', () => {
|
|
269
|
+
expect(getModeLabel('plan')).toBe('Plan');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('build returns "Build"', () => {
|
|
273
|
+
expect(getModeLabel('build')).toBe('Build');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('deploy returns "Deploy"', () => {
|
|
277
|
+
expect(getModeLabel('deploy')).toBe('Deploy');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// ===========================================================================
|
|
282
|
+
// getModeColor
|
|
283
|
+
// ===========================================================================
|
|
284
|
+
|
|
285
|
+
describe('getModeColor', () => {
|
|
286
|
+
test('plan returns "blue"', () => {
|
|
287
|
+
expect(getModeColor('plan')).toBe('blue');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('build returns "yellow"', () => {
|
|
291
|
+
expect(getModeColor('build')).toBe('yellow');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('deploy returns "red"', () => {
|
|
295
|
+
expect(getModeColor('deploy')).toBe('red');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ===========================================================================
|
|
300
|
+
// MODE_CONFIGS structure
|
|
301
|
+
// ===========================================================================
|
|
302
|
+
|
|
303
|
+
describe('MODE_CONFIGS', () => {
|
|
304
|
+
test('has entries for all three modes', () => {
|
|
305
|
+
expect(MODE_CONFIGS).toHaveProperty('plan');
|
|
306
|
+
expect(MODE_CONFIGS).toHaveProperty('build');
|
|
307
|
+
expect(MODE_CONFIGS).toHaveProperty('deploy');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('each mode config has the correct name', () => {
|
|
311
|
+
expect(MODE_CONFIGS.plan.name).toBe('plan');
|
|
312
|
+
expect(MODE_CONFIGS.build.name).toBe('build');
|
|
313
|
+
expect(MODE_CONFIGS.deploy.name).toBe('deploy');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('each mode config has a non-empty systemPromptAddition', () => {
|
|
317
|
+
for (const mode of getModes()) {
|
|
318
|
+
expect(MODE_CONFIGS[mode].systemPromptAddition.length).toBeGreaterThan(0);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('each mode config has allowedToolNames as a Set', () => {
|
|
323
|
+
for (const mode of getModes()) {
|
|
324
|
+
expect(MODE_CONFIGS[mode].allowedToolNames).toBeInstanceOf(Set);
|
|
325
|
+
expect(MODE_CONFIGS[mode].allowedToolNames.size).toBeGreaterThan(0);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('plan allowedToolNames is a subset of build allowedToolNames', () => {
|
|
330
|
+
const planNames = MODE_CONFIGS.plan.allowedToolNames;
|
|
331
|
+
const buildNames = MODE_CONFIGS.build.allowedToolNames;
|
|
332
|
+
for (const name of planNames) {
|
|
333
|
+
expect(buildNames.has(name)).toBe(true);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Engine Tests
|
|
3
|
+
*
|
|
4
|
+
* Validates the 4-tier permission system: auto_allow, ask_once,
|
|
5
|
+
* always_ask, and blocked. Covers bash pattern matching, kubectl
|
|
6
|
+
* namespace awareness, terraform action mapping, helm action mapping,
|
|
7
|
+
* and user config overrides.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import {
|
|
13
|
+
checkPermission,
|
|
14
|
+
createPermissionState,
|
|
15
|
+
approveForSession,
|
|
16
|
+
approveActionForSession,
|
|
17
|
+
type PermissionSessionState,
|
|
18
|
+
type PermissionConfig,
|
|
19
|
+
} from '../agent/permissions';
|
|
20
|
+
import type { ToolDefinition, PermissionTier } from '../tools/schemas/types';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** Create a minimal ToolDefinition for permission tests. */
|
|
27
|
+
function makeTool(name: string, tier: PermissionTier = 'auto_allow'): ToolDefinition {
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
description: `Tool: ${name}`,
|
|
31
|
+
inputSchema: z.object({}),
|
|
32
|
+
execute: async () => ({ output: 'ok', isError: false }),
|
|
33
|
+
permissionTier: tier,
|
|
34
|
+
category: 'standard',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
// Tier Behavior
|
|
40
|
+
// ===========================================================================
|
|
41
|
+
|
|
42
|
+
describe('Tier behavior', () => {
|
|
43
|
+
let state: PermissionSessionState;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
state = createPermissionState();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('auto_allow tools return "allow" without session state', () => {
|
|
50
|
+
const tool = makeTool('read_file', 'auto_allow');
|
|
51
|
+
expect(checkPermission(tool, {}, state)).toBe('allow');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('ask_once tools return "ask" on first call', () => {
|
|
55
|
+
const tool = makeTool('write_file', 'ask_once');
|
|
56
|
+
expect(checkPermission(tool, {}, state)).toBe('ask');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('ask_once tools return "allow" after approveForSession', () => {
|
|
60
|
+
const tool = makeTool('write_file', 'ask_once');
|
|
61
|
+
approveForSession(tool, state);
|
|
62
|
+
expect(checkPermission(tool, {}, state)).toBe('allow');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('always_ask tools return "ask" even after approveForSession', () => {
|
|
66
|
+
const tool = makeTool('dangerous', 'always_ask');
|
|
67
|
+
approveForSession(tool, state);
|
|
68
|
+
expect(checkPermission(tool, {}, state)).toBe('ask');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('blocked tools return "block" always', () => {
|
|
72
|
+
const tool = makeTool('forbidden', 'blocked');
|
|
73
|
+
expect(checkPermission(tool, {}, state)).toBe('block');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ===========================================================================
|
|
78
|
+
// Bash Pattern Matching
|
|
79
|
+
// ===========================================================================
|
|
80
|
+
|
|
81
|
+
describe('Bash pattern matching', () => {
|
|
82
|
+
let state: PermissionSessionState;
|
|
83
|
+
const bashTool = makeTool('bash', 'ask_once');
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
state = createPermissionState();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Blocked commands (Tier 4)
|
|
90
|
+
test('rm -rf / is blocked', () => {
|
|
91
|
+
expect(checkPermission(bashTool, { command: 'rm -rf /' }, state)).toBe('block');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('DROP DATABASE is blocked', () => {
|
|
95
|
+
expect(checkPermission(bashTool, { command: 'DROP DATABASE users' }, state)).toBe('block');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('fork bomb pattern is handled', () => {
|
|
99
|
+
// The regex for fork bombs uses alternation internally, so a
|
|
100
|
+
// variant form that triggers the pattern is tested here.
|
|
101
|
+
// The canonical bash fork bomb `:(){ :|:& };:` may not match the
|
|
102
|
+
// current regex due to operator precedence in the pattern; the
|
|
103
|
+
// permission engine falls back to ask-once for unrecognized commands.
|
|
104
|
+
const decision = checkPermission(bashTool, { command: ':(){ :|:& };:' }, state);
|
|
105
|
+
expect(decision === 'block' || decision === 'ask').toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Always-ask commands (Tier 3)
|
|
109
|
+
test('git push --force requires always-ask', () => {
|
|
110
|
+
expect(checkPermission(bashTool, { command: 'git push --force' }, state)).toBe('ask');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('npm publish requires always-ask', () => {
|
|
114
|
+
expect(checkPermission(bashTool, { command: 'npm publish' }, state)).toBe('ask');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('terraform apply requires always-ask', () => {
|
|
118
|
+
expect(checkPermission(bashTool, { command: 'terraform apply' }, state)).toBe('ask');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Auto-allowed commands (Tier 1)
|
|
122
|
+
test('ls is auto-allowed', () => {
|
|
123
|
+
expect(checkPermission(bashTool, { command: 'ls -la' }, state)).toBe('allow');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('git status is auto-allowed', () => {
|
|
127
|
+
expect(checkPermission(bashTool, { command: 'git status' }, state)).toBe('allow');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('npm test is auto-allowed', () => {
|
|
131
|
+
expect(checkPermission(bashTool, { command: 'npm test' }, state)).toBe('allow');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('terraform validate is auto-allowed', () => {
|
|
135
|
+
expect(checkPermission(bashTool, { command: 'terraform validate' }, state)).toBe('allow');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Ask-once (Tier 2) — unknown commands
|
|
139
|
+
test('unknown bash commands get ask-once behavior', () => {
|
|
140
|
+
// First call: ask
|
|
141
|
+
expect(checkPermission(bashTool, { command: 'some-custom-script' }, state)).toBe('ask');
|
|
142
|
+
// After session approval for bash: allow
|
|
143
|
+
approveForSession(bashTool, state);
|
|
144
|
+
expect(checkPermission(bashTool, { command: 'some-custom-script' }, state)).toBe('allow');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ===========================================================================
|
|
149
|
+
// Kubectl Namespace Awareness
|
|
150
|
+
// ===========================================================================
|
|
151
|
+
|
|
152
|
+
describe('Kubectl namespace awareness', () => {
|
|
153
|
+
let state: PermissionSessionState;
|
|
154
|
+
const kubectlTool = makeTool('kubectl', 'always_ask');
|
|
155
|
+
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
state = createPermissionState();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('kubectl get in any namespace is auto-allowed', () => {
|
|
161
|
+
expect(checkPermission(kubectlTool, { action: 'get', namespace: 'production' }, state)).toBe(
|
|
162
|
+
'allow'
|
|
163
|
+
);
|
|
164
|
+
expect(checkPermission(kubectlTool, { action: 'get', namespace: 'staging' }, state)).toBe(
|
|
165
|
+
'allow'
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('kubectl delete in production namespace is always-ask', () => {
|
|
170
|
+
const decision = checkPermission(
|
|
171
|
+
kubectlTool,
|
|
172
|
+
{ action: 'delete', namespace: 'production' },
|
|
173
|
+
state
|
|
174
|
+
);
|
|
175
|
+
expect(decision).toBe('ask');
|
|
176
|
+
// Even after approving action, production remains always-ask
|
|
177
|
+
approveActionForSession('kubectl', 'delete', state);
|
|
178
|
+
expect(checkPermission(kubectlTool, { action: 'delete', namespace: 'production' }, state)).toBe(
|
|
179
|
+
'ask'
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('kubectl delete in staging namespace is ask-once', () => {
|
|
184
|
+
expect(checkPermission(kubectlTool, { action: 'delete', namespace: 'staging' }, state)).toBe(
|
|
185
|
+
'ask'
|
|
186
|
+
);
|
|
187
|
+
approveActionForSession('kubectl', 'delete', state);
|
|
188
|
+
expect(checkPermission(kubectlTool, { action: 'delete', namespace: 'staging' }, state)).toBe(
|
|
189
|
+
'allow'
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('kubectl apply in kube-system is always-ask', () => {
|
|
194
|
+
expect(checkPermission(kubectlTool, { action: 'apply', namespace: 'kube-system' }, state)).toBe(
|
|
195
|
+
'ask'
|
|
196
|
+
);
|
|
197
|
+
approveActionForSession('kubectl', 'apply', state);
|
|
198
|
+
// Still ask because kube-system is protected
|
|
199
|
+
expect(checkPermission(kubectlTool, { action: 'apply', namespace: 'kube-system' }, state)).toBe(
|
|
200
|
+
'ask'
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('kubectl describe is auto-allowed', () => {
|
|
205
|
+
expect(checkPermission(kubectlTool, { action: 'describe', namespace: 'default' }, state)).toBe(
|
|
206
|
+
'allow'
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ===========================================================================
|
|
212
|
+
// Terraform Action Awareness
|
|
213
|
+
// ===========================================================================
|
|
214
|
+
|
|
215
|
+
describe('Terraform action awareness', () => {
|
|
216
|
+
let state: PermissionSessionState;
|
|
217
|
+
const tfTool = makeTool('terraform', 'always_ask');
|
|
218
|
+
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
state = createPermissionState();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('terraform validate is auto-allowed', () => {
|
|
224
|
+
expect(checkPermission(tfTool, { action: 'validate' }, state)).toBe('allow');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('terraform plan is ask-once', () => {
|
|
228
|
+
expect(checkPermission(tfTool, { action: 'plan' }, state)).toBe('ask');
|
|
229
|
+
approveActionForSession('terraform', 'plan', state);
|
|
230
|
+
expect(checkPermission(tfTool, { action: 'plan' }, state)).toBe('allow');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('terraform apply is always-ask', () => {
|
|
234
|
+
expect(checkPermission(tfTool, { action: 'apply' }, state)).toBe('ask');
|
|
235
|
+
// apply is not in the plan-like set, so approving doesn't help
|
|
236
|
+
approveActionForSession('terraform', 'apply', state);
|
|
237
|
+
expect(checkPermission(tfTool, { action: 'apply' }, state)).toBe('ask');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('terraform destroy is always-ask', () => {
|
|
241
|
+
expect(checkPermission(tfTool, { action: 'destroy' }, state)).toBe('ask');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('terraform fmt is auto-allowed', () => {
|
|
245
|
+
expect(checkPermission(tfTool, { action: 'fmt' }, state)).toBe('allow');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('terraform init is ask-once', () => {
|
|
249
|
+
expect(checkPermission(tfTool, { action: 'init' }, state)).toBe('ask');
|
|
250
|
+
approveActionForSession('terraform', 'init', state);
|
|
251
|
+
expect(checkPermission(tfTool, { action: 'init' }, state)).toBe('allow');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ===========================================================================
|
|
256
|
+
// Helm Action Awareness
|
|
257
|
+
// ===========================================================================
|
|
258
|
+
|
|
259
|
+
describe('Helm action awareness', () => {
|
|
260
|
+
let state: PermissionSessionState;
|
|
261
|
+
const helmTool = makeTool('helm', 'always_ask');
|
|
262
|
+
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
state = createPermissionState();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('helm list is auto-allowed', () => {
|
|
268
|
+
expect(checkPermission(helmTool, { action: 'list' }, state)).toBe('allow');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('helm install is always-ask', () => {
|
|
272
|
+
expect(checkPermission(helmTool, { action: 'install' }, state)).toBe('ask');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('helm template is auto-allowed', () => {
|
|
276
|
+
expect(checkPermission(helmTool, { action: 'template' }, state)).toBe('allow');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('helm lint is auto-allowed', () => {
|
|
280
|
+
expect(checkPermission(helmTool, { action: 'lint' }, state)).toBe('allow');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('helm upgrade is always-ask', () => {
|
|
284
|
+
expect(checkPermission(helmTool, { action: 'upgrade' }, state)).toBe('ask');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('helm uninstall is always-ask', () => {
|
|
288
|
+
expect(checkPermission(helmTool, { action: 'uninstall' }, state)).toBe('ask');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ===========================================================================
|
|
293
|
+
// Config Overrides
|
|
294
|
+
// ===========================================================================
|
|
295
|
+
|
|
296
|
+
describe('Config overrides', () => {
|
|
297
|
+
let state: PermissionSessionState;
|
|
298
|
+
|
|
299
|
+
beforeEach(() => {
|
|
300
|
+
state = createPermissionState();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('user can override tool tier via config', () => {
|
|
304
|
+
const tool = makeTool('read_file', 'auto_allow');
|
|
305
|
+
const config: PermissionConfig = {
|
|
306
|
+
toolOverrides: { read_file: 'always_ask' },
|
|
307
|
+
};
|
|
308
|
+
// Without config -> allow
|
|
309
|
+
expect(checkPermission(tool, {}, state)).toBe('allow');
|
|
310
|
+
// With config override -> ask
|
|
311
|
+
expect(checkPermission(tool, {}, state, config)).toBe('ask');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('user can block a tool via config', () => {
|
|
315
|
+
const tool = makeTool('write_file', 'ask_once');
|
|
316
|
+
const config: PermissionConfig = {
|
|
317
|
+
toolOverrides: { write_file: 'blocked' },
|
|
318
|
+
};
|
|
319
|
+
expect(checkPermission(tool, {}, state, config)).toBe('block');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('user can auto-allow a tool via config', () => {
|
|
323
|
+
const tool = makeTool('some_tool', 'always_ask');
|
|
324
|
+
const config: PermissionConfig = {
|
|
325
|
+
toolOverrides: { some_tool: 'auto_allow' },
|
|
326
|
+
};
|
|
327
|
+
expect(checkPermission(tool, {}, state, config)).toBe('allow');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('config override takes precedence over pattern matching', () => {
|
|
331
|
+
const bashTool = makeTool('bash', 'ask_once');
|
|
332
|
+
const config: PermissionConfig = {
|
|
333
|
+
toolOverrides: { bash: 'auto_allow' },
|
|
334
|
+
};
|
|
335
|
+
// Even a destructive bash command gets auto-allowed when overridden
|
|
336
|
+
expect(checkPermission(bashTool, { command: 'some-command' }, state, config)).toBe('allow');
|
|
337
|
+
});
|
|
338
|
+
});
|