|
|
@@ -1,8 +1,11 @@
|
|
|
import path from "path"
|
|
|
+import { pathToFileURL } from "url"
|
|
|
import z from "zod"
|
|
|
import { Tool } from "./tool"
|
|
|
import { Skill } from "../skill"
|
|
|
import { PermissionNext } from "../permission/next"
|
|
|
+import { Ripgrep } from "../file/ripgrep"
|
|
|
+import { iife } from "@/util/iife"
|
|
|
|
|
|
export const SkillTool = Tool.define("skill", async (ctx) => {
|
|
|
const skills = await Skill.all()
|
|
|
@@ -18,21 +21,29 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
|
|
|
|
|
|
const description =
|
|
|
accessibleSkills.length === 0
|
|
|
- ? "Load a skill to get detailed instructions for a specific task. No skills are currently available."
|
|
|
+ ? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
|
|
|
: [
|
|
|
- "Load a skill to get detailed instructions for a specific task.",
|
|
|
- "Skills provide specialized knowledge and step-by-step guidance.",
|
|
|
- "Use this when a task matches an available skill's description.",
|
|
|
- "Only the skills listed here are available:",
|
|
|
+ "Load a specialized skill that provides domain-specific instructions and workflows.",
|
|
|
+ "",
|
|
|
+ "When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions.",
|
|
|
+ "",
|
|
|
+ "The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context.",
|
|
|
+ "",
|
|
|
+ 'Tool output includes a `<skill_content name="...">` block with the loaded content.',
|
|
|
+ "",
|
|
|
+ "The following skills provide specialized sets of instructions for particular tasks",
|
|
|
+ "Invoke this tool to load a skill when a task matches one of the available skills listed below:",
|
|
|
+ "",
|
|
|
"<available_skills>",
|
|
|
...accessibleSkills.flatMap((skill) => [
|
|
|
` <skill>`,
|
|
|
` <name>${skill.name}</name>`,
|
|
|
` <description>${skill.description}</description>`,
|
|
|
+ ` <location>${pathToFileURL(skill.location).href}</location>`,
|
|
|
` </skill>`,
|
|
|
]),
|
|
|
"</available_skills>",
|
|
|
- ].join(" ")
|
|
|
+ ].join("\n")
|
|
|
|
|
|
const examples = accessibleSkills
|
|
|
.map((skill) => `'${skill.name}'`)
|
|
|
@@ -41,7 +52,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
|
|
|
const hint = examples.length > 0 ? ` (e.g., ${examples}, ...)` : ""
|
|
|
|
|
|
const parameters = z.object({
|
|
|
- name: z.string().describe(`The skill identifier from available_skills${hint}`),
|
|
|
+ name: z.string().describe(`The name of the skill from available_skills${hint}`),
|
|
|
})
|
|
|
|
|
|
return {
|
|
|
@@ -61,15 +72,47 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
|
|
|
always: [params.name],
|
|
|
metadata: {},
|
|
|
})
|
|
|
- const content = skill.content
|
|
|
+
|
|
|
const dir = path.dirname(skill.location)
|
|
|
+ const base = pathToFileURL(dir).href
|
|
|
|
|
|
- // Format output similar to plugin pattern
|
|
|
- const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", content.trim()].join("\n")
|
|
|
+ const limit = 10
|
|
|
+ const files = await iife(async () => {
|
|
|
+ const arr = []
|
|
|
+ for await (const file of Ripgrep.files({
|
|
|
+ cwd: dir,
|
|
|
+ follow: false,
|
|
|
+ hidden: true,
|
|
|
+ signal: ctx.abort,
|
|
|
+ })) {
|
|
|
+ if (file.includes("SKILL.md")) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ arr.push(path.resolve(dir, file))
|
|
|
+ if (arr.length >= limit) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return arr
|
|
|
+ }).then((f) => f.map((file) => `<file>${file}</file>`).join("\n"))
|
|
|
|
|
|
return {
|
|
|
title: `Loaded skill: ${skill.name}`,
|
|
|
- output,
|
|
|
+ output: [
|
|
|
+ `<skill_content name="${skill.name}">`,
|
|
|
+ `# Skill: ${skill.name}`,
|
|
|
+ "",
|
|
|
+ skill.content.trim(),
|
|
|
+ "",
|
|
|
+ `Base directory for this skill: ${base}`,
|
|
|
+ "Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.",
|
|
|
+ "Note: file list is sampled.",
|
|
|
+ "",
|
|
|
+ "<skill_files>",
|
|
|
+ files,
|
|
|
+ "</skill_files>",
|
|
|
+ "</skill_content>",
|
|
|
+ ].join("\n"),
|
|
|
metadata: {
|
|
|
name: skill.name,
|
|
|
dir,
|