4444import java .io .FileNotFoundException ;
4545import java .io .IOException ;
4646import java .nio .charset .StandardCharsets ;
47+ import java .nio .file .Paths ;
4748import java .util .List ;
4849import java .util .Map ;
4950import java .util .function .BiFunction ;
5556 * @author zlt
5657 */
5758public class SkillExample {
59+ private static final String PROJECT_SKILLS_PATH =
60+ Paths .get (System .getProperty ("user.dir" ),
61+ "examples" , "documentation" , "src" , "main" , "resources" , "skills" )
62+ .toString ();
63+
5864 // ==================== 基础注册表使用 ====================
5965
6066 /**
@@ -65,7 +71,7 @@ public static SkillRegistry fileSystemSkillRegistry() {
6571 // 配置从当前项目的 skills 目录加载
6672 // 目录结构示例: ./skills/my-skill/SKILL.md (每个技能是一个文件夹,且必须包含 SKILL.md)
6773 SkillRegistry registry = FileSystemSkillRegistry .builder ()
68- .projectSkillsDirectory (System . getProperty ( "user.dir" ) + "/src/main/resources/skills" )
74+ .projectSkillsDirectory (PROJECT_SKILLS_PATH )
6975 .build ();
7076
7177 System .out .println ("Registry created with file system" );
@@ -97,7 +103,7 @@ public static SkillRegistry multiLevelDirectory() {
97103 // 配置用户级和项目级目录
98104 SkillRegistry registry = FileSystemSkillRegistry .builder ()
99105 .userSkillsDirectory (System .getProperty ("user.home" ) + "/saa/skills" )
100- .projectSkillsDirectory (System . getProperty ( "user.dir" ) + "/src/main/resources/skills" )
106+ .projectSkillsDirectory (PROJECT_SKILLS_PATH )
101107 .build ();
102108
103109 System .out .println ("Registry created with multi-level directories" );
@@ -149,8 +155,23 @@ public static SkillsAgentHook progressiveToolDisclosure(SkillRegistry registry)
149155 // 需与 SKILL.md 元数据里的 name 一致
150156 String skillName = "weekly-report-assistant" ;
151157
158+ String basePath ;
159+ if (registry instanceof ClasspathSkillRegistry ) {
160+ // ClasspathSkillRegistry 将技能复制到 /tmp/skills
161+ basePath = "/tmp/skills" ;
162+ } else if (registry instanceof FileSystemSkillRegistry fsRegistry ) {
163+ basePath = fsRegistry .getProjectSkillsDirectory () != null
164+ ? fsRegistry .getProjectSkillsDirectory ()
165+ : fsRegistry .getUserSkillsDirectory ();
166+ } else {
167+ throw new IllegalArgumentException ("Unsupported registry type: " + registry .getClass ());
168+ }
169+
152170 // 定义一个特定技能才需要的工具,例如 weekly-report-assistant 使用 pythonTool 来运行 skill 里面的 python 脚本
153- ToolCallback pythonTool = PythonTool .createPythonToolCallback (PythonTool .DESCRIPTION );
171+ ToolCallback pythonTool = PythonTool .createPythonToolCallback (
172+ PythonTool .DESCRIPTION ,
173+ basePath
174+ );
154175
155176 // 将工具与技能名称绑定 (key 必须与 .md 文件中的 name 字段一致)
156177 Map <String , List <ToolCallback >> groupedTools = Map .of (
@@ -164,7 +185,7 @@ public static SkillsAgentHook progressiveToolDisclosure(SkillRegistry registry)
164185 .groupedTools (groupedTools ) // 注入工具绑定关系
165186 .build ();
166187
167- System .out .println ("Hook configured with progressive tool disclosure" );
188+ System .out .println ("Hook configured with progressive tool disclosure (base path: " + basePath + ") " );
168189 return hook ;
169190 }
170191
@@ -281,20 +302,27 @@ The tool will execute the logic and return the result (printed output or return
281302
282303 private static final Logger log = LoggerFactory .getLogger (PythonTool .class );
283304 private final Engine engine ;
305+ private final File allowedBasePath ;
284306
285- public PythonTool () {
307+ public PythonTool (String allowedBasePath ) {
286308 // Create a shared engine for better performance
287309 this .engine = Engine .newBuilder ()
288310 .option ("engine.WarnInterpreterOnly" , "false" )
289311 .build ();
312+ try {
313+ this .allowedBasePath = new File (allowedBasePath ).getCanonicalFile ();
314+ log .info ("PythonTool initialized with allowed base path: {}" , this .allowedBasePath );
315+ } catch (IOException e ) {
316+ throw new IllegalArgumentException ("Invalid base path: " + allowedBasePath , e );
317+ }
290318 Runtime .getRuntime ().addShutdownHook (new Thread (this ::close ));
291319 }
292320
293321 /**
294322 * Create a ToolCallback for the Python tool.
295323 */
296- public static ToolCallback createPythonToolCallback (String description ) {
297- return FunctionToolCallback .builder ("python_tool" , new PythonTool ())
324+ public static ToolCallback createPythonToolCallback (String description , String allowedBasePath ) {
325+ return FunctionToolCallback .builder ("python_tool" , new PythonTool (allowedBasePath ))
298326 .description (description )
299327 .inputType (PythonRequest .class )
300328 .build ();
@@ -406,10 +434,12 @@ public PythonRequest() {}
406434 }
407435
408436 private File getSafeScriptFile (String scriptPath ) throws IOException {
409- File baseDir = new File ("/tmp/skills" ).getCanonicalFile ();
410437 File targetFile = new File (scriptPath ).getCanonicalFile ();
411- if (!targetFile .getPath ().startsWith (baseDir .getPath ())) {
412- throw new SecurityException ("Access denied: Script path is outside the allowed directory." );
438+ if (!targetFile .getPath ().startsWith (allowedBasePath .getPath ())) {
439+ throw new SecurityException (
440+ String .format ("Access denied: Script path '%s' is outside the allowed directory '%s'." ,
441+ targetFile .getPath (), allowedBasePath .getPath ())
442+ );
413443 }
414444 if (!targetFile .exists () || !targetFile .isFile ()) {
415445 throw new FileNotFoundException ("Script not found: " + scriptPath );
0 commit comments