mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
feat: add backend route to delete custom mode files from disk
Replace the config disable:true approach with a proper Agent.remove() function that scans config directories for the agent's markdown file and deletes it, then refreshes state via Instance.dispose(). - Add Agent.remove() in agent.ts with RemoveError - Add POST /kilocode/agent/remove route - Mount KilocodeRoutes in server.ts - Update KiloProvider to call the new backend route - Regenerate SDK
This commit is contained in:
@@ -1162,25 +1162,30 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove (disable) a mode via config, then refresh agents.
|
||||
* Remove a custom mode via the CLI backend (deletes from disk + refreshes state).
|
||||
* The webview optimistically removes the mode from its list before this runs.
|
||||
* On failure, re-fetches agents so the webview reverts to the authoritative state.
|
||||
*/
|
||||
private async handleRemoveMode(name: string): Promise<void> {
|
||||
if (!this.client) return
|
||||
try {
|
||||
await this.client.global.config.update(
|
||||
{ config: { agent: { [name]: { disable: true } } } },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
// Invalidate cache so next requestAgents fetches fresh data
|
||||
this.cachedAgentsMessage = null
|
||||
await this.fetchAndSendAgents()
|
||||
const dir = this.getWorkspaceDirectory()
|
||||
const result = await this.client.kilocode.removeAgent({ name, directory: dir })
|
||||
if (result.error) {
|
||||
console.error("[Kilo New] KiloProvider: removeAgent returned error:", result.error)
|
||||
this.cachedAgentsMessage = null
|
||||
await this.fetchAndSendAgents()
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Kilo New] KiloProvider: Failed to remove mode:", error)
|
||||
this.cachedAgentsMessage = null
|
||||
await this.fetchAndSendAgents()
|
||||
return
|
||||
}
|
||||
// Invalidate cache so next requestAgents fetches fresh data
|
||||
this.cachedAgentsMessage = null
|
||||
await this.fetchAndSendAgents()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,8 @@ import PROMPT_ORCHESTRATOR from "./prompt/orchestrator.txt"
|
||||
import PROMPT_SUMMARY from "./prompt/summary.txt"
|
||||
import PROMPT_TITLE from "./prompt/title.txt"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
import { NamedError } from "@opencode-ai/util/error" // kilocode_change
|
||||
import { Glob } from "../util/glob" // kilocode_change
|
||||
import { mergeDeep, pipe, sortBy, values } from "remeda"
|
||||
import { Global } from "@/global"
|
||||
import path from "path"
|
||||
@@ -438,4 +440,49 @@ export namespace Agent {
|
||||
const result = await generateObject(params)
|
||||
return result.object
|
||||
}
|
||||
|
||||
// kilocode_change start
|
||||
export const RemoveError = NamedError.create(
|
||||
"AgentRemoveError",
|
||||
z.object({
|
||||
name: z.string(),
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
* Remove a custom agent by deleting its markdown file from disk.
|
||||
* Scans all config directories for agent/mode .md files matching the name.
|
||||
*/
|
||||
export async function remove(name: string) {
|
||||
const agents = await state()
|
||||
const agent = agents[name]
|
||||
if (!agent) throw new RemoveError({ name, message: "agent not found" })
|
||||
if (agent.native) throw new RemoveError({ name, message: "cannot remove native agent" })
|
||||
|
||||
const dirs = await Config.directories()
|
||||
const patterns = ["{agent,agents}/**/" + name + ".md", "{mode,modes}/" + name + ".md"]
|
||||
|
||||
let found = false
|
||||
for (const dir of dirs) {
|
||||
for (const pattern of patterns) {
|
||||
const matches = await Glob.scan(pattern, { cwd: dir, absolute: true, dot: true })
|
||||
for (const file of matches) {
|
||||
await Bun.file(file)
|
||||
.exists()
|
||||
.then(async (exists) => {
|
||||
if (!exists) return
|
||||
const { unlink } = await import("fs/promises")
|
||||
await unlink(file)
|
||||
found = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) throw new RemoveError({ name, message: "no agent file found on disk" })
|
||||
|
||||
await Instance.dispose()
|
||||
}
|
||||
// kilocode_change end
|
||||
}
|
||||
|
||||
@@ -5,38 +5,70 @@ import { Hono } from "hono"
|
||||
import { describeRoute, validator, resolver } from "hono-openapi"
|
||||
import z from "zod"
|
||||
import { Skill } from "../../skill/skill"
|
||||
import { Agent } from "../../agent/agent"
|
||||
import { lazy } from "../../util/lazy"
|
||||
import { errors } from "../error"
|
||||
|
||||
export const KilocodeRoutes = lazy(() =>
|
||||
new Hono().post(
|
||||
"/skill/remove",
|
||||
describeRoute({
|
||||
summary: "Remove a skill",
|
||||
description: "Remove a skill by deleting its directory from disk and clearing it from cache.",
|
||||
operationId: "kilocode.removeSkill",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Skill removed",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
new Hono()
|
||||
.post(
|
||||
"/skill/remove",
|
||||
describeRoute({
|
||||
summary: "Remove a skill",
|
||||
description: "Remove a skill by deleting its directory from disk and clearing it from cache.",
|
||||
operationId: "kilocode.removeSkill",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Skill removed",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
location: z.string(),
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
location: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { location } = c.req.valid("json")
|
||||
await Skill.remove(location)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/agent/remove",
|
||||
describeRoute({
|
||||
summary: "Remove a custom agent",
|
||||
description: "Remove a custom (non-native) agent by deleting its markdown file from disk and refreshing state.",
|
||||
operationId: "kilocode.removeAgent",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Agent removed",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...errors(400),
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { name } = c.req.valid("json")
|
||||
await Agent.remove(name)
|
||||
return c.json(true)
|
||||
},
|
||||
),
|
||||
async (c) => {
|
||||
const { location } = c.req.valid("json")
|
||||
await Skill.remove(location)
|
||||
return c.json(true)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -57,6 +57,8 @@ import type {
|
||||
KiloCloudSessionImportResponses,
|
||||
KiloCloudSessionsErrors,
|
||||
KiloCloudSessionsResponses,
|
||||
KilocodeRemoveAgentErrors,
|
||||
KilocodeRemoveAgentResponses,
|
||||
KilocodeRemoveSkillErrors,
|
||||
KilocodeRemoveSkillResponses,
|
||||
KiloFimErrors,
|
||||
@@ -2969,6 +2971,45 @@ export class Kilocode extends HeyApiClient {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a custom agent
|
||||
*
|
||||
* Remove a custom (non-native) agent by deleting its markdown file from disk and refreshing state.
|
||||
*/
|
||||
public removeAgent<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
name?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "body", key: "name" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<KilocodeRemoveAgentResponses, KilocodeRemoveAgentErrors, ThrowOnError>(
|
||||
{
|
||||
url: "/kilocode/agent/remove",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Organization extends HeyApiClient {
|
||||
|
||||
@@ -4432,6 +4432,36 @@ export type KilocodeRemoveSkillResponses = {
|
||||
|
||||
export type KilocodeRemoveSkillResponse = KilocodeRemoveSkillResponses[keyof KilocodeRemoveSkillResponses]
|
||||
|
||||
export type KilocodeRemoveAgentData = {
|
||||
body?: {
|
||||
name: string
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
}
|
||||
url: "/kilocode/agent/remove"
|
||||
}
|
||||
|
||||
export type KilocodeRemoveAgentErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type KilocodeRemoveAgentError = KilocodeRemoveAgentErrors[keyof KilocodeRemoveAgentErrors]
|
||||
|
||||
export type KilocodeRemoveAgentResponses = {
|
||||
/**
|
||||
* Agent removed
|
||||
*/
|
||||
200: boolean
|
||||
}
|
||||
|
||||
export type KilocodeRemoveAgentResponse = KilocodeRemoveAgentResponses[keyof KilocodeRemoveAgentResponses]
|
||||
|
||||
export type KiloProfileData = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
@@ -5438,6 +5438,72 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kilocode/agent/remove": {
|
||||
"post": {
|
||||
"operationId": "kilocode.removeAgent",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "workspace",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": "Remove a custom agent",
|
||||
"description": "Remove a custom (non-native) agent by deleting its markdown file from disk and refreshing state.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Agent removed",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilocode.removeAgent({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kilo/profile": {
|
||||
"get": {
|
||||
"operationId": "kilo.profile",
|
||||
|
||||
Reference in New Issue
Block a user