feat: add ability to remove discovered skills from Agent Behaviour settings

Add a remove button (X icon) to each discovered skill in the skills
subtab with a confirmation dialog. On confirm, the skill's directory
is deleted from disk and the skills list is refreshed.
This commit is contained in:
Mark IJbema
2026-03-16 13:04:07 +01:00
parent cc12e8d9c7
commit 2e4070d438
4 changed files with 79 additions and 12 deletions
+20
View File
@@ -429,6 +429,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "requestSkills":
this.fetchAndSendSkills().catch((e) => console.error("[Kilo New] fetchAndSendSkills failed:", e))
break
case "removeSkill":
this.handleRemoveSkill(message.location).catch((e) =>
console.error("[Kilo New] handleRemoveSkill failed:", e),
)
break
case "questionReply":
await this.handleQuestionReply(message.requestID, message.answers)
break
@@ -1127,6 +1132,21 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}
/**
* Remove a skill by deleting its directory from disk, then refresh the skills list.
* The location is the path to the SKILL.md file; the skill directory is its parent.
*/
private async handleRemoveSkill(location: string): Promise<void> {
const dir = path.dirname(location)
try {
await vscode.workspace.fs.delete(vscode.Uri.file(dir), { recursive: true })
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to delete skill directory:", dir, error)
}
this.cachedSkillsMessage = null
await this.fetchAndSendSkills()
}
/**
* Fetch backend config and send to webview.
*/
@@ -4,6 +4,8 @@ import { TextField } from "@kilocode/kilo-ui/text-field"
import { Card } from "@kilocode/kilo-ui/card"
import { Button } from "@kilocode/kilo-ui/button"
import { IconButton } from "@kilocode/kilo-ui/icon-button"
import { Dialog } from "@kilocode/kilo-ui/dialog"
import { useDialog } from "@kilocode/kilo-ui/context/dialog"
import { useConfig } from "../../context/config"
import { useSession } from "../../context/session"
@@ -53,6 +55,7 @@ const AgentBehaviourTab: Component = () => {
const { config, updateConfig } = useConfig()
const session = useSession()
const vscode = useVSCode()
const dialog = useDialog()
const [activeSubtab, setActiveSubtab] = createSignal<SubtabId>("agents")
const [selectedAgent, setSelectedAgent] = createSignal<string>("")
const [newSkillPath, setNewSkillPath] = createSignal("")
@@ -179,6 +182,35 @@ const AgentBehaviourTab: Component = () => {
updateConfig({ skills: { ...config().skills, urls: current } })
}
const removeSkill = (skill: SkillInfo) => {
vscode.postMessage({ type: "removeSkill", location: skill.location })
}
const confirmRemoveSkill = (skill: SkillInfo) => {
dialog.show(() => (
<Dialog title={language.t("settings.agentBehaviour.removeSkill.title")} fit>
<div class="dialog-confirm-body">
<span>{language.t("settings.agentBehaviour.removeSkill.confirm", { name: skill.name })}</span>
<div class="dialog-confirm-actions">
<Button variant="ghost" size="large" onClick={() => dialog.close()}>
{language.t("common.cancel")}
</Button>
<Button
variant="primary"
size="large"
onClick={() => {
removeSkill(skill)
dialog.close()
}}
>
{language.t("settings.agentBehaviour.removeSkill.button")}
</Button>
</div>
</div>
</Dialog>
))
}
const renderAgentsSubtab = () => (
<div>
{/* Default agent */}
@@ -410,24 +442,30 @@ const AgentBehaviourTab: Component = () => {
{(skill, index) => (
<div
style={{
display: "flex",
"align-items": "center",
"justify-content": "space-between",
padding: "8px 0",
"border-bottom":
index() < discoveredSkills().length - 1 ? "1px solid var(--border-weak-base)" : "none",
}}
>
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "0" }}>
{skill.name}
</div>
<div
data-slot="settings-row-label-subtitle"
style={{
"margin-top": "4px",
"font-family": "var(--vscode-editor-font-family, monospace)",
}}
>
<div>{skill.description}</div>
<div>{skill.location}</div>
<div style={{ flex: 1, "min-width": 0 }}>
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "0" }}>
{skill.name}
</div>
<div
data-slot="settings-row-label-subtitle"
style={{
"margin-top": "4px",
"font-family": "var(--vscode-editor-font-family, monospace)",
}}
>
<div>{skill.description}</div>
<div>{skill.location}</div>
</div>
</div>
<IconButton size="small" variant="ghost" icon="close" onClick={() => confirmRemoveSkill(skill)} />
</div>
)}
</For>
@@ -971,6 +971,9 @@ export const dict = {
"No skills discovered. Add skill folder paths or URLs below to make skills available.",
"settings.agentBehaviour.skillPaths": "Skill Folder Paths",
"settings.agentBehaviour.skillUrls": "Skill URLs",
"settings.agentBehaviour.removeSkill.title": "Remove skill",
"settings.agentBehaviour.removeSkill.confirm": 'Remove skill "{{name}}"? This will delete the skill files from disk.',
"settings.agentBehaviour.removeSkill.button": "Remove",
"settings.agentBehaviour.instructionFiles": "Additional Instruction Files",
"settings.agentBehaviour.instructionFiles.description":
"Paths to additional instruction files that are included in the system prompt",
@@ -1248,6 +1248,11 @@ export interface RequestSkillsMessage {
type: "requestSkills"
}
export interface RemoveSkillMessage {
type: "removeSkill"
location: string
}
export interface SetLanguageRequest {
type: "setLanguage"
locale: string
@@ -1628,6 +1633,7 @@ export type WebviewMessage =
| CompactRequest
| RequestAgentsMessage
| RequestSkillsMessage
| RemoveSkillMessage
| SetLanguageRequest
| QuestionReplyRequest
| QuestionRejectRequest